SqlTranslateService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq.Expressions;
  4. using Vit.Linq;
  5. using Vit.Linq.ExpressionNodes.ComponentModel;
  6. using Vitorm.Entity;
  7. using Vitorm.MySql.TranslateService;
  8. using Vitorm.Sql.SqlTranslate;
  9. namespace Vitorm.MySql
  10. {
  11. public class SqlTranslateService : Vitorm.Sql.SqlTranslate.SqlTranslateService
  12. {
  13. public static readonly SqlTranslateService Instance = new SqlTranslateService();
  14. protected override BaseQueryTranslateService queryTranslateService { get; }
  15. protected override BaseQueryTranslateService executeUpdateTranslateService { get; }
  16. protected override BaseQueryTranslateService executeDeleteTranslateService { get; }
  17. public SqlTranslateService()
  18. {
  19. queryTranslateService = new QueryTranslateService(this);
  20. executeUpdateTranslateService = new ExecuteUpdateTranslateService(this);
  21. executeDeleteTranslateService = new ExecuteDeleteTranslateService(this);
  22. }
  23. /// <summary>
  24. /// Generates the delimited SQL representation of an identifier (column name, table name, etc.).
  25. /// </summary>
  26. /// <param name="identifier">The identifier to delimit.</param>
  27. /// <returns>
  28. /// The generated string.
  29. /// </returns>
  30. public override string DelimitIdentifier(string identifier) => $"`{EscapeIdentifier(identifier)}`"; // Interpolation okay; strings
  31. /// <summary>
  32. /// Generates the escaped SQL representation of an identifier (column name, table name, etc.).
  33. /// </summary>
  34. /// <param name="identifier">The identifier to be escaped.</param>
  35. /// <returns>
  36. /// The generated string.
  37. /// </returns>
  38. public override string EscapeIdentifier(string identifier) => identifier?.Replace("`", "\\`");
  39. #region EvalExpression
  40. /// <summary>
  41. /// read where or value or on
  42. /// </summary>
  43. /// <param name="arg"></param>
  44. /// <returns></returns>
  45. /// <exception cref="NotSupportedException"></exception>
  46. /// <param name="node"></param>
  47. public override string EvalExpression(QueryTranslateArgument arg, ExpressionNode node)
  48. {
  49. switch (node.nodeType)
  50. {
  51. case NodeType.MethodCall:
  52. {
  53. ExpressionNode_MethodCall methodCall = node;
  54. switch (methodCall.methodName)
  55. {
  56. // ##1 ToString
  57. case nameof(object.ToString):
  58. {
  59. return $"cast({EvalExpression(arg, methodCall.@object)} as char)";
  60. }
  61. #region ##2 String method: StartsWith EndsWith Contains
  62. case nameof(string.StartsWith): // String.StartsWith
  63. {
  64. var str = methodCall.@object;
  65. var value = methodCall.arguments[0];
  66. return $"{EvalExpression(arg, str)} like concat({EvalExpression(arg, value)},'%')";
  67. }
  68. case nameof(string.EndsWith): // String.EndsWith
  69. {
  70. var str = methodCall.@object;
  71. var value = methodCall.arguments[0];
  72. return $"{EvalExpression(arg, str)} like concat('%',{EvalExpression(arg, value)})";
  73. }
  74. case nameof(string.Contains) when methodCall.methodCall_typeName == "String": // String.Contains
  75. {
  76. var str = methodCall.@object;
  77. var value = methodCall.arguments[0];
  78. return $"{EvalExpression(arg, str)} like concat('%',{EvalExpression(arg, value)},'%')";
  79. }
  80. #endregion
  81. }
  82. break;
  83. }
  84. #region Read Value
  85. case NodeType.Convert:
  86. {
  87. // cast( 4.1 as signed)
  88. ExpressionNode_Convert convert = node;
  89. Type targetType = convert.valueType?.ToType();
  90. if (targetType == typeof(object)) return EvalExpression(arg, convert.body);
  91. // Nullable
  92. if (targetType.IsGenericType) targetType = targetType.GetGenericArguments()[0];
  93. string targetDbType = GetColumnDbType(targetType);
  94. var sourceType = convert.body.Member_GetType();
  95. if (sourceType != null)
  96. {
  97. if (sourceType.IsGenericType) sourceType = sourceType.GetGenericArguments()[0];
  98. if (targetDbType == GetColumnDbType(sourceType)) return EvalExpression(arg, convert.body);
  99. }
  100. if (targetType == typeof(string))
  101. {
  102. return $"cast({EvalExpression(arg, convert.body)} as char)";
  103. }
  104. return $"cast({EvalExpression(arg, convert.body)} as {targetDbType})";
  105. }
  106. case nameof(ExpressionType.Add):
  107. {
  108. ExpressionNode_Binary binary = node;
  109. // ##1 String Add
  110. if (node.valueType?.ToType() == typeof(string))
  111. {
  112. // select ifNull( cast( (userFatherId) as char ) , '' ) from `User`
  113. return $"CONCAT( {BuildSqlSentence(binary.left)} , {BuildSqlSentence(binary.right)} )";
  114. string BuildSqlSentence(ExpressionNode node)
  115. {
  116. if (node.nodeType == NodeType.Constant)
  117. {
  118. ExpressionNode_Constant constant = node;
  119. if (constant.value == null) return "''";
  120. else return $"cast( ({EvalExpression(arg, node)}) as char )";
  121. }
  122. else
  123. return $"ifNull( cast( ({EvalExpression(arg, node)}) as char ) , '')";
  124. }
  125. }
  126. // ##2 Numeric Add
  127. return $"{EvalExpression(arg, binary.left)} + {EvalExpression(arg, binary.right)}";
  128. }
  129. case nameof(ExpressionType.Coalesce):
  130. {
  131. ExpressionNode_Binary binary = node;
  132. return $"COALESCE({EvalExpression(arg, binary.left)},{EvalExpression(arg, binary.right)})";
  133. }
  134. case nameof(ExpressionType.Conditional):
  135. {
  136. // IF(500<1000,true,false)
  137. ExpressionNode_Conditional conditional = node;
  138. return $"IF({EvalExpression(arg, conditional.Conditional_GetTest())},{EvalExpression(arg, conditional.Conditional_GetIfTrue())},{EvalExpression(arg, conditional.Conditional_GetIfFalse())})";
  139. }
  140. #endregion
  141. }
  142. return base.EvalExpression(arg, node);
  143. }
  144. #endregion
  145. #region PrepareCreate
  146. public override string PrepareTryCreateTable(IEntityDescriptor entityDescriptor)
  147. {
  148. /* //sql
  149. CREATE TABLE IF NOT EXISTS `User` (
  150. id int PRIMARY KEY AUTO_INCREMENT NOT NULL,
  151. name varchar(100) DEFAULT NULL,
  152. birth date DEFAULT NULL,
  153. fatherId int DEFAULT NULL,
  154. motherId int DEFAULT NULL
  155. ) ;
  156. */
  157. List<string> sqlFields = new();
  158. // #1 primary key
  159. if (entityDescriptor.key != null)
  160. sqlFields.Add(GetColumnSql(entityDescriptor.key));
  161. // #2 columns
  162. entityDescriptor.propertiesWithoutKey?.ForEach(column => sqlFields.Add(GetColumnSql(column)));
  163. return $@"
  164. CREATE TABLE IF NOT EXISTS {DelimitTableName(entityDescriptor)} (
  165. {string.Join(",\r\n ", sqlFields)}
  166. )";
  167. string GetColumnSql(IPropertyDescriptor column)
  168. {
  169. var isNullable = !column.isKey && column.isNullable;
  170. var columnDbType = column.columnDbType ?? GetColumnDbType(column);
  171. var defaultValue = isNullable ? "default null" : "";
  172. if (column.isIdentity)
  173. {
  174. var type = TypeUtil.GetUnderlyingType(column.type);
  175. if (type == typeof(Guid)) { }
  176. else defaultValue = "AUTO_INCREMENT";
  177. }
  178. var nullable = isNullable ? "" : "not null";
  179. // https://mysql.net.cn/doc/refman/8.0/en/create-table.html
  180. /*
  181. name type primaryKey defaultValue nullable
  182. id int primary key default null not null/null
  183. AUTO_INCREMENT
  184. */
  185. return $" {DelimitIdentifier(column.columnName)} {columnDbType} {(column.isKey ? "primary key" : "")} {defaultValue} {nullable}";
  186. }
  187. }
  188. public readonly static Dictionary<Type, string> columnDbTypeMap = new()
  189. {
  190. [typeof(DateTime)] = "DATETIME",
  191. [typeof(string)] = "varchar(1000)",
  192. [typeof(float)] = "FLOAT",
  193. [typeof(double)] = "DOUBLE",
  194. [typeof(decimal)] = "DOUBLE",
  195. [typeof(Int32)] = "INTEGER",
  196. [typeof(Int16)] = "SMALLINT",
  197. [typeof(byte)] = "TINYINT",
  198. [typeof(bool)] = "TINYINT",
  199. [typeof(Guid)] = "binary(16)",
  200. };
  201. protected override string GetColumnDbType(IPropertyDescriptor column)
  202. {
  203. Type type = column.type;
  204. if (column.isKey && column.type == typeof(string))
  205. {
  206. // avoid issue: SQL Error [1071] [42000]: Specified key was too long; max key length is 3072 bytes.
  207. // example "CREATE TABLE test ( `name` varchar(1000) primary key);"
  208. return "varchar(200)";
  209. }
  210. if (column.columnLength.HasValue && type == typeof(string))
  211. {
  212. // varchar(1000)
  213. return $"varchar({(column.columnLength.Value)})";
  214. }
  215. return GetColumnDbType(type);
  216. }
  217. protected override string GetColumnDbType(Type type)
  218. {
  219. var underlyingType = TypeUtil.GetUnderlyingType(type);
  220. if (columnDbTypeMap.TryGetValue(underlyingType, out var dbType)) return dbType;
  221. if (underlyingType.Name.ToLower().Contains("int")) return "INTEGER";
  222. throw new NotSupportedException("unsupported column type:" + underlyingType.Name);
  223. }
  224. #endregion
  225. public override string PrepareTryDropTable(IEntityDescriptor entityDescriptor)
  226. {
  227. // drop table if exists `User`;
  228. return $@"drop table if exists {DelimitTableName(entityDescriptor)};";
  229. }
  230. public override (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareAdd(SqlTranslateArgument arg, EAddType addType)
  231. {
  232. if (addType == EAddType.identityKey)
  233. {
  234. // insert into user(name,fatherId,motherId) values('',0,0); select last_insert_id();
  235. var entityDescriptor = arg.entityDescriptor;
  236. var (columnNames, sqlColumnParams, GetSqlParams) = PrepareAdd_Columns(arg, entityDescriptor.propertiesWithoutKey);
  237. string sql = $@"insert into {DelimitTableName(entityDescriptor)}({string.Join(",", columnNames)}) values({string.Join(",", sqlColumnParams)});";
  238. // get generated id
  239. sql += "select last_insert_id();";
  240. return (sql, GetSqlParams);
  241. }
  242. else
  243. {
  244. // insert into user(name,fatherId,motherId) values('',0,0);
  245. var entityDescriptor = arg.entityDescriptor;
  246. var (columnNames, sqlColumnParams, GetSqlParams) = PrepareAdd_Columns(arg, entityDescriptor.properties);
  247. string sql = $@"insert into {DelimitTableName(entityDescriptor)}({string.Join(",", columnNames)}) values({string.Join(",", sqlColumnParams)});";
  248. return (sql, GetSqlParams);
  249. }
  250. }
  251. }
  252. }