SqlTranslateService.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. using System;
  2. using System.Collections.Generic;
  3. using Vit.Linq.ExpressionTree.ComponentModel;
  4. using Vitorm.Entity;
  5. using System.Linq;
  6. using Vitorm.StreamQuery;
  7. using System.Collections;
  8. using System.Text;
  9. namespace Vitorm.Sql.SqlTranslate
  10. {
  11. public abstract class SqlTranslateService : ISqlTranslateService
  12. {
  13. public SqlTranslateService()
  14. {
  15. }
  16. #region DelimitIdentifier
  17. /// <summary>
  18. /// Generates the delimited SQL representation of an identifier (column name, table name, etc.).
  19. /// </summary>
  20. /// <param name="identifier">The identifier to delimit.</param>
  21. /// <returns>
  22. /// The generated string.
  23. /// </returns>
  24. public virtual string DelimitIdentifier(string identifier) => $"\"{EscapeIdentifier(identifier)}\""; // Interpolation okay; strings
  25. /// <summary>
  26. /// Generates the escaped SQL representation of an identifier (column name, table name, etc.).
  27. /// </summary>
  28. /// <param name="identifier">The identifier to be escaped.</param>
  29. /// <returns>
  30. /// The generated string.
  31. /// </returns>
  32. public virtual string EscapeIdentifier(string identifier) => identifier.Replace("\"", "\"\"");
  33. /// <summary>
  34. /// Generates a valid parameter name for the given candidate name.
  35. /// </summary>
  36. /// <param name="name">The candidate name for the parameter.</param>
  37. /// <returns>
  38. /// A valid name based on the candidate name.
  39. /// </returns>
  40. public virtual string GenerateParameterName(string name) => name.StartsWith("@", StringComparison.Ordinal) ? name : "@" + name;
  41. public virtual string DelimitTableName(IEntityDescriptor entityDescriptor) => DelimitIdentifier(entityDescriptor.tableName);
  42. #endregion
  43. public virtual string GetSqlField(string tableName, string columnName)
  44. {
  45. return $"{DelimitIdentifier(tableName)}.{DelimitIdentifier(columnName)}";
  46. }
  47. /// <summary>
  48. /// user.id
  49. /// </summary>
  50. /// <param name="member"></param>
  51. /// <param name="dbContext"></param>
  52. /// <returns></returns>
  53. public virtual string GetSqlField(ExpressionNode_Member member, DbContext dbContext)
  54. {
  55. var memberName = member.memberName;
  56. if (string.IsNullOrWhiteSpace(memberName))
  57. {
  58. var entityType = member.Member_GetType();
  59. memberName = dbContext.GetEntityDescriptor(entityType)?.keyName;
  60. }
  61. // 1: {"nodeType":"Member","parameterName":"a0","memberName":"id"}
  62. // 2: {"nodeType":"Member","objectValue":{"parameterName":"a0","nodeType":"Member"},"memberName":"id"}
  63. return GetSqlField(member.objectValue?.parameterName ?? member.parameterName, memberName);
  64. }
  65. protected abstract string GetDbType(Type type);
  66. #region EvalExpression
  67. /// <summary>
  68. /// read where or value or on
  69. /// </summary>
  70. /// <param name="arg"></param>
  71. /// <returns></returns>
  72. /// <exception cref="NotSupportedException"></exception>
  73. /// <param name="data"></param>
  74. public virtual string EvalExpression(QueryTranslateArgument arg, ExpressionNode data)
  75. {
  76. switch (data.nodeType)
  77. {
  78. case NodeType.And:
  79. ExpressionNode_And and = data;
  80. return $"({EvalExpression(arg, and.left)}) and ({EvalExpression(arg, and.right)})";
  81. case NodeType.Or:
  82. ExpressionNode_Or or = data;
  83. return $"({EvalExpression(arg, or.left)}) or ({EvalExpression(arg, or.right)})";
  84. case NodeType.Not:
  85. ExpressionNode_Not not = data;
  86. return $"not ({EvalExpression(arg, not.body)})";
  87. case NodeType.ArrayIndex:
  88. throw new NotSupportedException(data.nodeType);
  89. //ExpressionNode_ArrayIndex arrayIndex = data;
  90. //return Expression.ArrayIndex(ToExpression(arg, arrayIndex.left), ToExpression(arg, arrayIndex.right));
  91. case NodeType.Equal:
  92. case NodeType.NotEqual:
  93. {
  94. ExpressionNode_Binary binary = data;
  95. // "= null" -> "is null" , "!=null" -> "is not null"
  96. if (binary.right.nodeType == NodeType.Constant && binary.right.value == null)
  97. {
  98. var opera = data.nodeType == NodeType.Equal ? "is null" : "is not null";
  99. return $"{EvalExpression(arg, binary.left)} " + opera;
  100. }
  101. else if (binary.left.nodeType == NodeType.Constant && binary.left.value == null)
  102. {
  103. var opera = data.nodeType == NodeType.Equal ? "is null" : "is not null";
  104. return $"{EvalExpression(arg, binary.right)} " + opera;
  105. }
  106. var @operator = operatorMap[data.nodeType];
  107. return $"{EvalExpression(arg, binary.left)} {@operator} {EvalExpression(arg, binary.right)}";
  108. }
  109. case NodeType.LessThan:
  110. case NodeType.LessThanOrEqual:
  111. case NodeType.GreaterThan:
  112. case NodeType.GreaterThanOrEqual:
  113. {
  114. ExpressionNode_Binary binary = data;
  115. var @operator = operatorMap[data.nodeType];
  116. return $"{EvalExpression(arg, binary.left)} {@operator} {EvalExpression(arg, binary.right)}";
  117. }
  118. case NodeType.MethodCall:
  119. {
  120. ExpressionNode_MethodCall methodCall = data;
  121. switch (methodCall.methodName)
  122. {
  123. // ##1 in
  124. case nameof(Enumerable.Contains):
  125. {
  126. var values = methodCall.arguments[0];
  127. var member = methodCall.arguments[1];
  128. return $"{EvalExpression(arg, member)} in {EvalExpression(arg, values)}";
  129. }
  130. // ##2 db primitive function
  131. case nameof(DbFunction.Call):
  132. {
  133. var functionName = methodCall.arguments[0].value as string;
  134. var argList = methodCall.arguments.AsQueryable().Skip(1).Select(argNode => EvalExpression(arg, argNode)).ToList();
  135. var funcArgs = string.Join(",", argList);
  136. return $"{functionName}({funcArgs})";
  137. }
  138. #region ##3 Aggregate
  139. case nameof(Enumerable.Count) when methodCall.arguments.Length == 1:
  140. {
  141. var stream = methodCall.arguments[0] as ExpressionNode_Member;
  142. //if (stream?.nodeType != NodeType.Member) break;
  143. return "Count(*)";
  144. }
  145. case nameof(Enumerable.Max) or nameof(Enumerable.Min) or nameof(Enumerable.Sum) or nameof(Enumerable.Average) when methodCall.arguments.Length == 2:
  146. {
  147. var stream = methodCall.arguments[0] as ExpressionNode_Member;
  148. if (stream?.nodeType != NodeType.Member) break;
  149. var lambdaFieldSelect = methodCall.arguments[1] as ExpressionNode_Lambda;
  150. var parameterName = lambdaFieldSelect.parameterNames[0];
  151. var parameterValue = (ExpressionNode)stream;
  152. Func<ExpressionNode_Member, ExpressionNode> GetParameter = (member) =>
  153. {
  154. if (member.nodeType == NodeType.Member && member.parameterName == parameterName)
  155. {
  156. if (string.IsNullOrWhiteSpace(member.memberName))
  157. {
  158. return parameterValue;
  159. }
  160. else
  161. {
  162. return ExpressionNode.Member(objectValue: parameterValue, memberName: member.memberName).Member_SetType(member.Member_GetType());
  163. }
  164. }
  165. return default;
  166. };
  167. var body = StreamReader.DeepClone(lambdaFieldSelect.body, GetParameter);
  168. var funcName = methodCall.methodName;
  169. if (funcName == nameof(Enumerable.Average)) funcName = "AVG";
  170. return $"{funcName}({EvalExpression(arg, body)})";
  171. }
  172. #endregion
  173. }
  174. throw new NotSupportedException("[QueryTranslator] not suported MethodCall: " + methodCall.methodName);
  175. }
  176. #region Read Value
  177. case NodeType.Member:
  178. return GetSqlField(data, arg.dbContext);
  179. case NodeType.Constant:
  180. {
  181. ExpressionNode_Constant constant = data;
  182. var value = constant.value;
  183. if (value == null)
  184. {
  185. return "null";
  186. }
  187. else if (value is not string && value is IEnumerable enumerable)
  188. {
  189. StringBuilder sql = null;
  190. foreach (var item in enumerable)
  191. {
  192. if (item == null) continue;
  193. if (sql == null)
  194. {
  195. sql = new StringBuilder("(");
  196. var paramName = arg.NewParamName();
  197. arg.sqlParam[paramName] = item;
  198. sql.Append(GenerateParameterName(paramName));
  199. }
  200. else
  201. {
  202. var paramName = arg.NewParamName();
  203. arg.sqlParam[paramName] = item;
  204. sql.Append(",").Append(GenerateParameterName(paramName));
  205. }
  206. }
  207. if (sql == null) return "(null)";
  208. return sql.Append(")").ToString();
  209. }
  210. else
  211. {
  212. var paramName = arg.NewParamName();
  213. arg.sqlParam[paramName] = value;
  214. return GenerateParameterName(paramName);
  215. }
  216. }
  217. #endregion
  218. }
  219. throw new NotSupportedException("[QueryTranslator] not suported nodeType: " + data.nodeType);
  220. }
  221. protected readonly static Dictionary<string, string> operatorMap = new Dictionary<string, string>
  222. {
  223. [NodeType.Equal] = "=",
  224. [NodeType.NotEqual] = "!=",
  225. [NodeType.LessThan] = "<",
  226. [NodeType.LessThanOrEqual] = "<=",
  227. [NodeType.GreaterThan] = ">",
  228. [NodeType.GreaterThanOrEqual] = ">=",
  229. };
  230. #endregion
  231. // #0 Schema : PrepareCreate
  232. public abstract string PrepareCreate(IEntityDescriptor entityDescriptor);
  233. #region #1 Create : PrepareAdd
  234. public virtual (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareAdd(SqlTranslateArgument arg)
  235. {
  236. /* //sql
  237. insert into user(name,birth,fatherId,motherId) values('','','');
  238. select seq from sqlite_sequence where name='user';
  239. */
  240. var entityDescriptor = arg.entityDescriptor;
  241. var columns = entityDescriptor.columns;
  242. // #1 GetSqlParams
  243. Func<object, Dictionary<string, object>> GetSqlParams = (entity) =>
  244. {
  245. var sqlParam = new Dictionary<string, object>();
  246. foreach (var column in columns)
  247. {
  248. var columnName = column.name;
  249. var value = column.GetValue(entity);
  250. sqlParam[columnName] = value;
  251. }
  252. return sqlParam;
  253. };
  254. #region #2 columns
  255. List<string> columnNames = new List<string>();
  256. List<string> valueParams = new List<string>();
  257. string columnName;
  258. foreach (var column in columns)
  259. {
  260. columnName = column.name;
  261. columnNames.Add(DelimitIdentifier(columnName));
  262. valueParams.Add(GenerateParameterName(columnName));
  263. }
  264. #endregion
  265. // #3 build sql
  266. string sql = $@"insert into {DelimitTableName(entityDescriptor)}({string.Join(",", columnNames)}) values({string.Join(",", valueParams)});";
  267. //sql+=$"select seq from sqlite_sequence where name = '{tableName}'; ";
  268. return (sql, GetSqlParams);
  269. }
  270. #endregion
  271. #region #2 Retrieve : PrepareGet PrepareQuery
  272. public virtual string PrepareGet(SqlTranslateArgument arg)
  273. {
  274. var entityDescriptor = arg.entityDescriptor;
  275. // #2 build sql
  276. string sql = $@"select * from {DelimitTableName(entityDescriptor)} where {DelimitIdentifier(entityDescriptor.keyName)}={GenerateParameterName(entityDescriptor.keyName)};";
  277. return sql;
  278. }
  279. public abstract (string sql, Dictionary<string, object> sqlParam, IDbDataReader dataReader) PrepareQuery(QueryTranslateArgument arg, CombinedStream combinedStream);
  280. #endregion
  281. #region #3 Update: PrepareUpdate PrepareExecuteUpdate
  282. public virtual (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareUpdate(SqlTranslateArgument arg)
  283. {
  284. /* //sql
  285. update user set name='' where id=7;
  286. */
  287. var entityDescriptor = arg.entityDescriptor;
  288. var sqlParam = new Dictionary<string, object>();
  289. // #1 GetSqlParams
  290. Func<object, Dictionary<string, object>> GetSqlParams = (entity) =>
  291. {
  292. var sqlParam = new Dictionary<string, object>();
  293. foreach (var column in entityDescriptor.allColumns)
  294. {
  295. var columnName = column.name;
  296. var value = column.GetValue(entity);
  297. sqlParam[columnName] = value;
  298. }
  299. //sqlParam[entityDescriptor.keyName] = entityDescriptor.key.Get(entity);
  300. return sqlParam;
  301. };
  302. // #2 columns
  303. List<string> columnsToUpdate = new List<string>();
  304. string columnName;
  305. foreach (var column in entityDescriptor.columns)
  306. {
  307. columnName = column.name;
  308. columnsToUpdate.Add($"{DelimitIdentifier(columnName)}={GenerateParameterName(columnName)}");
  309. }
  310. // #3 build sql
  311. string sql = $@"update {DelimitTableName(entityDescriptor)} set {string.Join(",", columnsToUpdate)} where {DelimitIdentifier(entityDescriptor.keyName)}={GenerateParameterName(entityDescriptor.keyName)};";
  312. return (sql, GetSqlParams);
  313. }
  314. public abstract (string sql, Dictionary<string, object> sqlParam) PrepareExecuteUpdate(QueryTranslateArgument arg, CombinedStream combinedStream);
  315. #endregion
  316. #region #4 Delete: PrepareDelete PrepareDeleteRange PrepareExecuteDelete
  317. public virtual string PrepareDelete(SqlTranslateArgument arg)
  318. {
  319. /* //sql
  320. delete from user where id = 7;
  321. */
  322. var entityDescriptor = arg.entityDescriptor;
  323. // #2 build sql
  324. string sql = $@"delete from {DelimitTableName(entityDescriptor)} where {DelimitIdentifier(entityDescriptor.keyName)}={GenerateParameterName(entityDescriptor.keyName)} ; ";
  325. return sql;
  326. }
  327. public virtual (string sql, Dictionary<string, object> sqlParam) PrepareDeleteByKeys<Key>(SqlTranslateArgument arg, IEnumerable<Key> keys)
  328. {
  329. // delete from user where id in ( 7 ) ;
  330. var entityDescriptor = arg.entityDescriptor;
  331. StringBuilder sql = new StringBuilder();
  332. Dictionary<string, object> sqlParam = new();
  333. sql.Append("delete from ").Append(DelimitTableName(entityDescriptor)).Append(" where ").Append(DelimitIdentifier(entityDescriptor.keyName)).Append(" in (");
  334. int keyIndex = 0;
  335. foreach (var key in keys)
  336. {
  337. var paramName = "p" + (keyIndex++);
  338. sql.Append(GenerateParameterName(paramName)).Append(",");
  339. sqlParam[paramName] = key;
  340. }
  341. if (keyIndex == 0) sql.Append("null);");
  342. else
  343. {
  344. sql.Length--;
  345. sql.Append(");");
  346. }
  347. return (sql.ToString(), sqlParam);
  348. }
  349. public abstract (string sql, Dictionary<string, object> sqlParam) PrepareExecuteDelete(QueryTranslateArgument arg, CombinedStream combinedStream);
  350. #endregion
  351. }
  352. }