SqlTranslateService.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq.Expressions;
  4. using Vit.Linq;
  5. using Vit.Linq.ExpressionTree.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="data"></param>
  47. public override string EvalExpression(QueryTranslateArgument arg, ExpressionNode data)
  48. {
  49. switch (data.nodeType)
  50. {
  51. case NodeType.MethodCall:
  52. {
  53. ExpressionNode_MethodCall methodCall = data;
  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 = data;
  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 = data;
  109. // ##1 String Add
  110. if (data.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 Numberic Add
  127. return $"{EvalExpression(arg, binary.left)} + {EvalExpression(arg, binary.right)}";
  128. }
  129. case nameof(ExpressionType.Coalesce):
  130. {
  131. ExpressionNode_Binary binary = data;
  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 = data;
  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, data);
  143. }
  144. #endregion
  145. #region PrepareCreate
  146. public override string PrepareTryCreateTable(IEntityDescriptor entityDescriptor)
  147. {
  148. /* //sql
  149. CREATE TABLE user (
  150. id int NOT NULL PRIMARY KEY,
  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) + " PRIMARY KEY " + (entityDescriptor.key.isIdentity ? "AUTO_INCREMENT " : ""));
  161. // #2 columns
  162. entityDescriptor.columns?.ForEach(column => sqlFields.Add(GetColumnSql(column)));
  163. return $@"
  164. CREATE TABLE {DelimitTableName(entityDescriptor)} (
  165. {string.Join(",\r\n ", sqlFields)}
  166. )";
  167. string GetColumnSql(IColumnDescriptor column)
  168. {
  169. var columnDbType = column.databaseType ?? GetColumnDbType(column.type);
  170. // name varchar(100) DEFAULT NULL
  171. return $" {DelimitIdentifier(column.columnName)} {columnDbType} {(column.isNullable ? "DEFAULT NULL" : "NOT NULL")}";
  172. }
  173. }
  174. protected override string GetColumnDbType(Type type)
  175. {
  176. type = TypeUtil.GetUnderlyingType(type);
  177. if (type == typeof(DateTime))
  178. return "DATETIME";
  179. if (type == typeof(string))
  180. return "varchar(1000)";
  181. if (type == typeof(float)) return "FLOAT";
  182. if (type == typeof(double) || type == typeof(decimal))
  183. return "DOUBLE";
  184. if (type == typeof(Int32)) return "INTEGER";
  185. if (type == typeof(Int16)) return "SMALLINT";
  186. if (type == typeof(byte)) return "TINYINT";
  187. if (type == typeof(bool)) return "TINYINT";
  188. if (type.Name.ToLower().Contains("int")) return "INTEGER";
  189. throw new NotSupportedException("unsupported column type:" + type.Name);
  190. }
  191. #endregion
  192. public override string PrepareTryDropTable(IEntityDescriptor entityDescriptor)
  193. {
  194. // DROP TABLE if exists `User`;
  195. return $@" DROP TABLE if exists {DelimitTableName(entityDescriptor)};";
  196. }
  197. public override (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareIdentityAdd(SqlTranslateArgument arg)
  198. {
  199. var result = PrepareAdd(arg, arg.entityDescriptor.columns);
  200. // get generated id
  201. result.sql += "select last_insert_id();";
  202. return result;
  203. }
  204. }
  205. }