PageRenderTime 26ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/class/System.Data.Linq/src/DbLinq/Data/Linq/Sugar/Implementation/QueryBuilder.Upsert.cs

https://bitbucket.org/danipen/mono
C# | 342 lines | 212 code | 17 blank | 113 comment | 31 complexity | 59ec83cd503e70e22d892a84c889302c MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. #region MIT license
  2. //
  3. // MIT license
  4. //
  5. // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. //
  25. #endregion
  26. using System;
  27. using System.Collections.Generic;
  28. using System.Data.Linq.Mapping;
  29. using System.Linq.Expressions;
  30. using System.Reflection;
  31. using DbLinq.Data.Linq.Sql;
  32. using DbLinq.Data.Linq.Sugar.Expressions;
  33. using DbLinq.Util;
  34. namespace DbLinq.Data.Linq.Sugar.Implementation
  35. {
  36. partial class QueryBuilder
  37. {
  38. protected class UpsertParameters
  39. {
  40. public MetaTable Table;
  41. public readonly IList<ObjectInputParameterExpression> InputParameters = new List<ObjectInputParameterExpression>();
  42. public readonly IList<ObjectOutputParameterExpression> OutputParameters = new List<ObjectOutputParameterExpression>();
  43. public readonly IList<ObjectInputParameterExpression> PKParameters = new List<ObjectInputParameterExpression>();
  44. public readonly IList<SqlStatement> InputColumns = new List<SqlStatement>();
  45. public readonly IList<SqlStatement> InputValues = new List<SqlStatement>();
  46. public readonly IList<SqlStatement> OutputColumns = new List<SqlStatement>();
  47. public readonly IList<SqlStatement> OutputValues = new List<SqlStatement>();
  48. public readonly IList<SqlStatement> OutputExpressions = new List<SqlStatement>();
  49. public readonly IList<SqlStatement> AutoPKColumns = new List<SqlStatement>();
  50. public readonly IList<SqlStatement> InputPKColumns = new List<SqlStatement>();
  51. public readonly IList<SqlStatement> InputPKValues = new List<SqlStatement>();
  52. public readonly IList<SqlStatement> PKColumns = new List<SqlStatement>();
  53. public readonly IList<SqlStatement> PKValues = new List<SqlStatement>();
  54. }
  55. // SQLite:
  56. // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, Expression = null
  57. // INSERT INTO main.Products (CategoryID, Discontinued, ProductName, QuantityPerUnit)
  58. // VALUES (@P1, @P2, @P3, @P4) ;SELECT last_insert_rowid()
  59. //
  60. // Ingres:
  61. // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false,
  62. // Expression = "next value for \"linquser\".\"products_seq\"")]
  63. // INSERT INTO linquser.products (categoryid, discontinued, productid, productname, quantityperunit)
  64. // VALUES ($param_000001_param$, $param_000002_param$,
  65. // next value for "linquser"."products_seq", $param_000004_param$, $param_000005_param$)
  66. //
  67. // Oracle:
  68. // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, Expression = null
  69. // BEGIN
  70. // INSERT INTO NORTHWIND."Products" ("CategoryID", "Discontinued", "ProductID", "ProductName", "QuantityPerUnit")
  71. // VALUES (:P1, :P2, NORTHWIND."Products_SEQ".NextVal, :P4, :P5)
  72. // ;SELECT NORTHWIND."Products_SEQ".CurrVal INTO :P3 FROM DUAL; END;
  73. //
  74. // PostgreSQL:
  75. // IsPrimaryKey = true, IsDbGenerated = true, CanBeNull = false, Expression = "nextval('\"Products_ProductID_seq\"')"
  76. // INSERT INTO public."Products" ("CategoryID", "Discontinued", "ProductName", "QuantityPerUnit")
  77. // VALUES (:P1, :P2, :P3, :P4)
  78. // ;SELECT currval('"Products_ProductID_seq"')
  79. //
  80. // SQL Server (bogus):
  81. // IsPrimaryKey = true, IsDbGenerated = true
  82. // INSERT INTO [dbo].[Products] (, , , ) VALUES (@P1, @P2, @P3, @P4)
  83. // ; SELECT @@IDENTITY
  84. //
  85. // Column: default --> use value
  86. // PK: Expression !null --> use parameter (Oracle is wrong here)
  87. // Expression null --> ignore
  88. // SQL: wrap clause with PK information
  89. /// <summary>
  90. /// Creates a query for insertion
  91. /// </summary>
  92. /// <param name="objectToInsert"></param>
  93. /// <param name="queryContext"></param>
  94. /// <returns></returns>
  95. public UpsertQuery GetInsertQuery(object objectToInsert, QueryContext queryContext)
  96. {
  97. // TODO: cache
  98. var upsertParameters = GetUpsertParameters(objectToInsert, false, null, queryContext);
  99. var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
  100. var insertSql = sqlProvider.GetInsert(
  101. sqlProvider.GetTable(upsertParameters.Table.TableName),
  102. upsertParameters.InputColumns,
  103. upsertParameters.InputValues);
  104. var insertIdSql = sqlProvider.GetInsertIds(
  105. sqlProvider.GetTable(upsertParameters.Table.TableName),
  106. upsertParameters.AutoPKColumns,
  107. upsertParameters.PKColumns,
  108. upsertParameters.PKValues,
  109. upsertParameters.OutputColumns,
  110. upsertParameters.OutputValues,
  111. upsertParameters.OutputExpressions);
  112. return new UpsertQuery(queryContext.DataContext, insertSql, insertIdSql, upsertParameters.InputParameters, upsertParameters.OutputParameters, upsertParameters.PKParameters);
  113. }
  114. protected enum ParameterType
  115. {
  116. Input,
  117. InputPK,
  118. Output,
  119. AutoSync
  120. }
  121. /// <summary>
  122. /// Gets values for insert/update
  123. /// </summary>
  124. /// <param name="objectToUpsert"></param>
  125. /// <param name="queryContext"></param>
  126. /// <param name="update"></param>
  127. /// <param name="modifiedMembers"></param>
  128. /// <returns></returns>
  129. protected virtual UpsertParameters GetUpsertParameters(object objectToUpsert, bool update, IList<MemberInfo> modifiedMembers, QueryContext queryContext)
  130. {
  131. var rowType = objectToUpsert.GetType();
  132. var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
  133. var upsertParameters = new UpsertParameters
  134. {
  135. Table = queryContext.DataContext.Mapping.GetTable(rowType)
  136. };
  137. foreach (var dataMember in upsertParameters.Table.RowType.PersistentDataMembers)
  138. {
  139. var column = sqlProvider.GetColumn(dataMember.MappedName);
  140. ParameterType type = GetParameterType(objectToUpsert, dataMember, update, update ? AutoSync.OnUpdate : AutoSync.OnInsert);
  141. var memberInfo = dataMember.Member;
  142. // if the column is generated AND not specified, we may have:
  143. // - an explicit generation (Expression property is not null, so we add the column)
  144. // - an implicit generation (Expression property is null
  145. // in all cases, we want to get the value back
  146. var getter = (Expression<Func<object, object>>)(o => memberInfo.GetMemberValue(o));
  147. var inputParameter = new ObjectInputParameterExpression(
  148. getter,
  149. memberInfo.GetMemberType(), dataMember.Name);
  150. if (dataMember.IsPrimaryKey && (! dataMember.IsDbGenerated))
  151. {
  152. upsertParameters.PKColumns.Add(column);
  153. upsertParameters.PKParameters.Add(inputParameter);
  154. upsertParameters.PKValues.Add(sqlProvider.GetParameterName(inputParameter.Alias));
  155. }
  156. if (type == ParameterType.Output)
  157. {
  158. if (dataMember.Expression != null)
  159. {
  160. upsertParameters.InputColumns.Add(column);
  161. upsertParameters.InputValues.Add(dataMember.Expression);
  162. }
  163. var setter = (Expression<Action<object, object>>)((o, v) => memberInfo.SetMemberValue(o, v));
  164. var outputParameter = new ObjectOutputParameterExpression(setter,
  165. memberInfo.GetMemberType(),
  166. dataMember.Name);
  167. if ((dataMember.IsPrimaryKey) && (dataMember.IsDbGenerated))
  168. upsertParameters.AutoPKColumns.Add(column);
  169. upsertParameters.OutputColumns.Add(column);
  170. upsertParameters.OutputParameters.Add(outputParameter);
  171. upsertParameters.OutputValues.Add(sqlProvider.GetParameterName(outputParameter.Alias));
  172. upsertParameters.OutputExpressions.Add(dataMember.Expression);
  173. }
  174. else // standard column
  175. {
  176. if (type == ParameterType.InputPK)
  177. {
  178. upsertParameters.InputPKColumns.Add(column);
  179. upsertParameters.InputPKValues.Add(sqlProvider.GetParameterName(inputParameter.Alias));
  180. upsertParameters.InputParameters.Add(inputParameter);
  181. }
  182. // for a standard column, we keep it only if modifiedMembers contains the specified memberInfo
  183. // caution: this makes the cache harder to maintain
  184. else if (modifiedMembers == null || modifiedMembers.Contains(memberInfo))
  185. {
  186. upsertParameters.InputColumns.Add(column);
  187. upsertParameters.InputValues.Add(sqlProvider.GetParameterName(inputParameter.Alias));
  188. upsertParameters.InputParameters.Add(inputParameter);
  189. }
  190. if (type == ParameterType.AutoSync)
  191. {
  192. var setter = (Expression<Action<object, object>>)((o, v) => memberInfo.SetMemberValue(o, v));
  193. var outputParameter = new ObjectOutputParameterExpression(setter,
  194. memberInfo.GetMemberType(),
  195. dataMember.Name);
  196. upsertParameters.OutputColumns.Add(column);
  197. upsertParameters.OutputParameters.Add(outputParameter);
  198. upsertParameters.OutputValues.Add(sqlProvider.GetParameterName(outputParameter.Alias));
  199. upsertParameters.OutputExpressions.Add(dataMember.Expression);
  200. }
  201. }
  202. }
  203. return upsertParameters;
  204. }
  205. /// <summary>
  206. /// Provides the parameter type for a given data member
  207. /// </summary>
  208. /// <param name="objectToUpsert"></param>
  209. /// <param name="dataMember"></param>
  210. /// <param name="update"></param>
  211. /// <returns></returns>
  212. protected virtual ParameterType GetParameterType(object objectToUpsert, MetaDataMember dataMember, bool update, AutoSync autoSync)
  213. {
  214. var memberInfo = dataMember.Member;
  215. // the deal with columns is:
  216. // PK only: criterion for INSERT, criterion for UPDATE
  217. // PK+GEN: implicit/criterion for INSERT, criterion for UPDATE
  218. // GEN only: implicit for both
  219. // -: explicit for both
  220. //
  221. // explicit is input,
  222. // implicit is output,
  223. // criterion is input PK
  224. ParameterType type;
  225. if (dataMember.IsPrimaryKey)
  226. {
  227. if (update)
  228. type = ParameterType.InputPK;
  229. else
  230. {
  231. if (dataMember.IsDbGenerated)
  232. {
  233. if (IsSpecified(objectToUpsert, memberInfo))
  234. type = ParameterType.Input;
  235. else
  236. type = ParameterType.Output;
  237. }
  238. else
  239. type = ParameterType.Input;
  240. }
  241. }
  242. else
  243. {
  244. if (dataMember.IsDbGenerated)
  245. type = ParameterType.Output;
  246. else if ((dataMember.AutoSync == AutoSync.Always) || (dataMember.AutoSync == autoSync))
  247. type = ParameterType.AutoSync;
  248. else
  249. type = ParameterType.Input;
  250. }
  251. return type;
  252. }
  253. /// <summary>
  254. /// Determines if a property is different from its default value
  255. /// </summary>
  256. /// <param name="target"></param>
  257. /// <param name="memberInfo"></param>
  258. /// <returns></returns>
  259. protected virtual bool IsSpecified(object target, MemberInfo memberInfo)
  260. {
  261. object value = memberInfo.GetMemberValue(target);
  262. if (value == null)
  263. return false;
  264. if (Equals(value, TypeConvert.GetDefault(memberInfo.GetMemberType())))
  265. return false;
  266. return true;
  267. }
  268. /// <summary>
  269. /// Creates or gets an UPDATE query
  270. /// </summary>
  271. /// <param name="objectToUpdate"></param>
  272. /// <param name="modifiedMembers">List of modified members, or NULL</param>
  273. /// <param name="queryContext"></param>
  274. /// <returns></returns>
  275. public UpsertQuery GetUpdateQuery(object objectToUpdate, IList<MemberInfo> modifiedMembers, QueryContext queryContext)
  276. {
  277. var upsertParameters = GetUpsertParameters(objectToUpdate, true, modifiedMembers, queryContext);
  278. var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
  279. var updateSql = sqlProvider.GetUpdate(sqlProvider.GetTable(upsertParameters.Table.TableName),
  280. upsertParameters.InputColumns, upsertParameters.InputValues,
  281. upsertParameters.OutputValues, upsertParameters.OutputExpressions,
  282. upsertParameters.InputPKColumns, upsertParameters.InputPKValues
  283. );
  284. var insertIdSql = (upsertParameters.OutputValues.Count == 0) ? "" :
  285. sqlProvider.GetInsertIds(
  286. sqlProvider.GetTable(upsertParameters.Table.TableName),
  287. upsertParameters.AutoPKColumns,
  288. upsertParameters.PKColumns,
  289. upsertParameters.PKValues,
  290. upsertParameters.OutputColumns,
  291. upsertParameters.OutputValues,
  292. upsertParameters.OutputExpressions);
  293. return new UpsertQuery(queryContext.DataContext, updateSql, insertIdSql, upsertParameters.InputParameters, upsertParameters.OutputParameters, upsertParameters.PKParameters);
  294. }
  295. /// <summary>
  296. /// Creates or gets a DELETE query
  297. /// </summary>
  298. /// <param name="objectToDelete"></param>
  299. /// <param name="queryContext"></param>
  300. /// <returns></returns>
  301. public DeleteQuery GetDeleteQuery(object objectToDelete, QueryContext queryContext)
  302. {
  303. var sqlProvider = queryContext.DataContext.Vendor.SqlProvider;
  304. var rowType = objectToDelete.GetType();
  305. var table = queryContext.DataContext.Mapping.GetTable(rowType);
  306. var deleteParameters = new List<ObjectInputParameterExpression>();
  307. var pkColumns = new List<SqlStatement>();
  308. var pkValues = new List<SqlStatement>();
  309. foreach (var pkMember in table.RowType.IdentityMembers)
  310. {
  311. var memberInfo = pkMember.Member;
  312. var getter = (Expression<Func<object, object>>)(o => memberInfo.GetMemberValue(o));
  313. var inputParameter = new ObjectInputParameterExpression(
  314. getter,
  315. memberInfo.GetMemberType(), pkMember.Name);
  316. var column = sqlProvider.GetColumn(pkMember.MappedName);
  317. pkColumns.Add(column);
  318. pkValues.Add(sqlProvider.GetParameterName(inputParameter.Alias));
  319. deleteParameters.Add(inputParameter);
  320. }
  321. var deleteSql = sqlProvider.GetDelete(sqlProvider.GetTable(table.TableName), pkColumns, pkValues);
  322. return new DeleteQuery(queryContext.DataContext, deleteSql, deleteParameters);
  323. }
  324. }
  325. }