SqliteDbMaintenance.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. using Microsoft.Data.Sqlite;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Data.Common;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Text.RegularExpressions;
  10. namespace SqlSugar
  11. {
  12. public class SqliteDbMaintenance : DbMaintenanceProvider
  13. {
  14. #region DML
  15. protected override string GetDataBaseSql
  16. {
  17. get
  18. {
  19. throw new NotSupportedException();
  20. }
  21. }
  22. protected override string GetColumnInfosByTableNameSql
  23. {
  24. get
  25. {
  26. throw new NotSupportedException();
  27. }
  28. }
  29. protected override string GetTableInfoListSql
  30. {
  31. get
  32. {
  33. return @"select Name from sqlite_master where type='table' and name<>'sqlite_sequence' order by name;";
  34. }
  35. }
  36. protected override string GetViewInfoListSql
  37. {
  38. get
  39. {
  40. return @"select Name from sqlite_master where type='view' order by name;";
  41. }
  42. }
  43. #endregion
  44. #region DDL
  45. protected override string CreateDataBaseSql
  46. {
  47. get
  48. {
  49. return "CREATE DATABASE {0}";
  50. }
  51. }
  52. protected override string AddPrimaryKeySql
  53. {
  54. get
  55. {
  56. throw new NotSupportedException();
  57. }
  58. }
  59. protected override string AddColumnToTableSql
  60. {
  61. get
  62. {
  63. return "ALTER TABLE {0} ADD COLUMN {1} {2}{3}";
  64. }
  65. }
  66. protected override string AlterColumnToTableSql
  67. {
  68. get
  69. {
  70. // return "ALTER TABLE {0} ALTER COLUMN {1} {2}{3} {4} {5} {6}";
  71. throw new NotSupportedException();
  72. }
  73. }
  74. protected override string BackupDataBaseSql
  75. {
  76. get
  77. {
  78. throw new NotSupportedException();
  79. }
  80. }
  81. protected override string CreateTableSql
  82. {
  83. get
  84. {
  85. return "CREATE TABLE {0}(\r\n{1} $PrimaryKey )";
  86. }
  87. }
  88. protected override string CreateTableColumn
  89. {
  90. get
  91. {
  92. return "{0} {1}{2} {3} {4} {5}";
  93. }
  94. }
  95. protected override string TruncateTableSql
  96. {
  97. get
  98. {
  99. return "DELETE FROM {0}";
  100. }
  101. }
  102. protected override string BackupTableSql
  103. {
  104. get
  105. {
  106. return " CREATE TABLE {0} AS SELECT * FROM {1} limit 0,{2}";
  107. }
  108. }
  109. protected override string DropTableSql
  110. {
  111. get
  112. {
  113. return "DROP TABLE {0}";
  114. }
  115. }
  116. protected override string DropColumnToTableSql
  117. {
  118. get
  119. {
  120. return "ALTER TABLE {0} DROP {1}";
  121. }
  122. }
  123. protected override string DropConstraintSql
  124. {
  125. get
  126. {
  127. throw new NotSupportedException();
  128. }
  129. }
  130. protected override string RenameColumnSql
  131. {
  132. get
  133. {
  134. throw new NotSupportedException();
  135. }
  136. }
  137. protected override string IsAnyProcedureSql => throw new NotImplementedException();
  138. #endregion
  139. #region Check
  140. protected override string CheckSystemTablePermissionsSql
  141. {
  142. get
  143. {
  144. return "select Name from sqlite_master limit 0,1";
  145. }
  146. }
  147. #endregion
  148. #region Scattered
  149. protected override string CreateTableNull
  150. {
  151. get
  152. {
  153. return "NULL";
  154. }
  155. }
  156. protected override string CreateTableNotNull
  157. {
  158. get
  159. {
  160. return "NOT NULL";
  161. }
  162. }
  163. protected override string CreateTablePirmaryKey
  164. {
  165. get
  166. {
  167. return "PRIMARY KEY";
  168. }
  169. }
  170. protected override string CreateTableIdentity
  171. {
  172. get
  173. {
  174. return "AUTOINCREMENT";
  175. }
  176. }
  177. protected override string AddColumnRemarkSql
  178. {
  179. get
  180. {
  181. throw new NotSupportedException();
  182. }
  183. }
  184. protected override string DeleteColumnRemarkSql
  185. {
  186. get
  187. {
  188. throw new NotSupportedException();
  189. }
  190. }
  191. protected override string IsAnyColumnRemarkSql
  192. {
  193. get
  194. {
  195. throw new NotSupportedException();
  196. }
  197. }
  198. protected override string AddTableRemarkSql
  199. {
  200. get
  201. {
  202. throw new NotSupportedException();
  203. }
  204. }
  205. protected override string DeleteTableRemarkSql
  206. {
  207. get
  208. {
  209. throw new NotSupportedException();
  210. }
  211. }
  212. protected override string IsAnyTableRemarkSql
  213. {
  214. get
  215. {
  216. throw new NotSupportedException();
  217. }
  218. }
  219. protected override string RenameTableSql
  220. {
  221. get
  222. {
  223. return "alter table {0} rename to {1}";
  224. }
  225. }
  226. protected override string CreateIndexSql
  227. {
  228. get
  229. {
  230. return "CREATE {3} INDEX Index_{0}_{2} ON {0}({1})";
  231. }
  232. }
  233. protected override string IsAnyIndexSql
  234. {
  235. get
  236. {
  237. return "SELECT count(*) FROM sqlite_master WHERE name = '{0}'";
  238. }
  239. }
  240. protected override string AddDefaultValueSql => throw new NotSupportedException(" Sqlite no support default value");
  241. #endregion
  242. #region Methods
  243. public override bool UpdateColumn(string tableName, DbColumnInfo column)
  244. {
  245. var isTran = this.Context.Ado.IsNoTran();
  246. try
  247. {
  248. if (column.IsPrimarykey)
  249. {
  250. Check.ExceptionEasy("Sqlite no support alter column primary key","Sqlite不支持修改主键");
  251. }
  252. if (isTran)
  253. // Start a transaction
  254. this.Context.Ado.BeginTran();
  255. tableName = tableName ?? column.TableName;
  256. var oldColumn = column.DbColumnName;
  257. var tempColumn = "Column" + SnowFlakeSingle.Instance.NextId();
  258. // Step 1: Add a new column
  259. column.DbColumnName = tempColumn;
  260. this.AddColumn(tableName, column);
  261. // Step 2: Update values from old column to new column
  262. this.Context.Ado.ExecuteCommand($"UPDATE { SqlBuilder.GetTranslationColumnName(tableName)} SET {SqlBuilder.GetTranslationColumnName(column.DbColumnName)}={SqlBuilder.GetTranslationColumnName(oldColumn)}");
  263. // Step 3: Drop the old column
  264. this.DropColumn(tableName, oldColumn);
  265. // Step 4: Rename the new column to the old column name
  266. column.DbColumnName = oldColumn;
  267. this.AddColumn(tableName, column);
  268. // Step 5: Update values from temporary column to the new column
  269. this.Context.Ado.ExecuteCommand($"UPDATE {SqlBuilder.GetTranslationColumnName(tableName)} SET {SqlBuilder.GetTranslationColumnName(column.DbColumnName)}={SqlBuilder.GetTranslationColumnName(tempColumn)}");
  270. //Step 6: Drop the temporary column
  271. this.DropColumn(tableName, tempColumn);
  272. if (isTran)
  273. // Commit the transaction
  274. this.Context.Ado.CommitTran();
  275. return true;
  276. }
  277. catch (Exception)
  278. {
  279. if (isTran)
  280. // Handle exceptions, log, or rollback the transaction if necessary
  281. this.Context.Ado.RollbackTran();
  282. // Log the exception or throw it again based on your requirements
  283. throw;
  284. }
  285. }
  286. public override List<string> GetDbTypes()
  287. {
  288. return this.Context.Ado.SqlQuery<string>(@"SELECT 'TEXT' AS Data_Type
  289. UNION
  290. SELECT 'INTEGER'
  291. UNION
  292. SELECT 'REAL'
  293. UNION
  294. SELECT 'BLOB'
  295. UNION SELECT 'bigint'
  296. UNION SELECT 'binary'
  297. UNION SELECT 'bit'
  298. UNION SELECT 'char'
  299. UNION SELECT 'date'
  300. UNION SELECT 'datetime'
  301. UNION SELECT 'datetime2'
  302. UNION SELECT 'datetimeoffset'
  303. UNION SELECT 'decimal'
  304. UNION SELECT 'float'
  305. UNION SELECT 'image'
  306. UNION SELECT 'int'
  307. UNION SELECT 'money'
  308. UNION SELECT 'nchar'
  309. UNION SELECT 'ntext'
  310. UNION SELECT 'numeric'
  311. UNION SELECT 'nvarchar'
  312. UNION SELECT 'smalldatetime'
  313. UNION SELECT 'smallint'
  314. UNION SELECT 'text'
  315. UNION SELECT 'time'
  316. UNION SELECT 'timestamp'
  317. UNION SELECT 'tinyint'
  318. UNION SELECT 'uniqueidentifier'
  319. UNION SELECT 'varbinary'
  320. UNION SELECT 'varchar'
  321. UNION SELECT 'xml' ");
  322. }
  323. public override List<string> GetTriggerNames(string tableName)
  324. {
  325. return this.Context.Ado.SqlQuery<string>(@"SELECT name
  326. FROM sqlite_master
  327. WHERE type = 'trigger'
  328. AND sql LIKE '%" + tableName + "%'");
  329. }
  330. public override List<string> GetFuncList()
  331. {
  332. return this.Context.Ado.SqlQuery<string>(" SELECT name\r\nFROM sqlite_master\r\nWHERE type = 'table' AND name NOT LIKE 'sqlite_%' ");
  333. }
  334. public override List<string> GetIndexList(string tableName)
  335. {
  336. var sql = $"PRAGMA index_list('{tableName}');";
  337. return this.Context.Ado.SqlQuery<string>(sql);
  338. }
  339. public override void AddDefaultValue(EntityInfo entityInfo)
  340. {
  341. //sqlite no support AddDefaultValue
  342. }
  343. public override bool AddDefaultValue(string tableName, string columnName, string defaultValue)
  344. {
  345. Console.WriteLine("sqlite no support AddDefaultValue");
  346. return true;
  347. }
  348. public override bool TruncateTable(string tableName)
  349. {
  350. base.TruncateTable(tableName);//delete data
  351. try
  352. {
  353. //clear sqlite identity
  354. return this.Context.Ado.ExecuteCommand($"UPDATE sqlite_sequence SET seq = 0 WHERE name = '{tableName}'") > 0;
  355. }
  356. catch
  357. {
  358. //if no identity sqlite_sequence
  359. return true;
  360. }
  361. }
  362. /// <summary>
  363. ///by current connection string
  364. /// </summary>
  365. /// <param name="databaseDirectory"></param>
  366. /// <returns></returns>
  367. public override bool CreateDatabase(string databaseName, string databaseDirectory = null)
  368. {
  369. var connString = this.Context.CurrentConnectionConfig.ConnectionString;
  370. var path = Regex.Match(connString, @"[a-z,A-Z]\:\\.+\\").Value;
  371. if (path.IsNullOrEmpty())
  372. {
  373. path = Regex.Match(connString, @"\/.+\/").Value;
  374. }
  375. if (path.IsNullOrEmpty())
  376. {
  377. path = Regex.Match(connString, @"[a-z,A-Z]\:\\").Value;
  378. }
  379. if (!FileHelper.IsExistDirectory(path))
  380. {
  381. FileHelper.CreateDirectory(path);
  382. }
  383. this.Context.Ado.Connection.Open();
  384. this.Context.Ado.Connection.Close();
  385. return true;
  386. }
  387. public override List<DbColumnInfo> GetColumnInfosByTableName(string tableName, bool isCache = true)
  388. {
  389. string cacheKey = "DbMaintenanceProvider.GetColumnInfosByTableName." + this.SqlBuilder.GetNoTranslationColumnName(tableName).ToLower();
  390. cacheKey = GetCacheKey(cacheKey);
  391. if (isCache)
  392. {
  393. return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate<List<DbColumnInfo>>(cacheKey, () =>
  394. {
  395. return GetColumnInfosByTableName(tableName);
  396. });
  397. }
  398. else
  399. {
  400. return GetColumnInfosByTableName(tableName);
  401. }
  402. }
  403. private List<DbColumnInfo> GetColumnInfosByTableName(string tableName)
  404. {
  405. //var columns = GetColumnsByTableName2(tableName);
  406. string sql = "PRAGMA table_info(" +SqlBuilder.GetTranslationColumnName(tableName) + ")";
  407. var oldIsEnableLog = this.Context.Ado.IsEnableLogEvent;
  408. this.Context.Ado.IsEnableLogEvent = false;
  409. var tableSript=this.Context.Ado.GetString($"SELECT sql FROM sqlite_master WHERE name='{tableName}' AND type='table'");
  410. using (DbDataReader dataReader = (SqliteDataReader)this.Context.Ado.GetDataReader(sql))
  411. {
  412. this.Context.Ado.IsEnableLogEvent = oldIsEnableLog;
  413. List<DbColumnInfo> result = new List<DbColumnInfo>();
  414. while (dataReader.Read())
  415. {
  416. var type = dataReader.GetValue(2).ObjToString();
  417. var length = 0;
  418. var decimalDigits = 0;
  419. if (type.Contains("("))
  420. {
  421. if (type.Contains(","))
  422. {
  423. var digit = type.Split('(').Last().TrimEnd(')');
  424. decimalDigits = digit.Split(',').Last().ObjToInt();
  425. length = digit.Split(',').First().ObjToInt();
  426. }
  427. else
  428. {
  429. length = type.Split('(').Last().TrimEnd(')').ObjToInt();
  430. }
  431. type = type.Split('(').First();
  432. }
  433. //bool isIdentity = columns.FirstOrDefault(it => it.DbColumnName.Equals(dataReader.GetString(1),StringComparison.CurrentCultureIgnoreCase)).IsIdentity;
  434. DbColumnInfo column = new DbColumnInfo()
  435. {
  436. TableName = this.SqlBuilder.GetNoTranslationColumnName(tableName + ""),
  437. DataType = type,
  438. IsNullable = !dataReader.GetBoolean(3),
  439. IsIdentity = tableSript.Contains("AUTOINCREMENT")&& dataReader.GetBoolean(5).ObjToBool(),
  440. ColumnDescription = null,
  441. DbColumnName = dataReader.GetString(1),
  442. DefaultValue = dataReader.GetValue(4).ObjToString(),
  443. IsPrimarykey = dataReader.GetBoolean(5).ObjToBool(),
  444. Length = length,
  445. DecimalDigits=decimalDigits,
  446. Scale= decimalDigits
  447. };
  448. result.Add(column);
  449. }
  450. return result;
  451. }
  452. }
  453. private List<DbColumnInfo> GetColumnsByTableName2(string tableName)
  454. {
  455. tableName = SqlBuilder.GetTranslationTableName(tableName);
  456. string sql = "select * from " + tableName + " limit 0,1";
  457. var oldIsEnableLog = this.Context.Ado.IsEnableLogEvent;
  458. this.Context.Ado.IsEnableLogEvent = false;
  459. using (DbDataReader reader = (SqliteDataReader)this.Context.Ado.GetDataReader(sql))
  460. {
  461. this.Context.Ado.IsEnableLogEvent = oldIsEnableLog;
  462. List<DbColumnInfo> result = new List<DbColumnInfo>();
  463. var schemaTable = reader.GetSchemaTable();
  464. foreach (DataRow row in schemaTable.Rows)
  465. {
  466. DbColumnInfo column = new DbColumnInfo()
  467. {
  468. TableName = tableName,
  469. IsIdentity = (bool)row["IsAutoIncrement"],
  470. DbColumnName = row["ColumnName"].ToString(),
  471. };
  472. result.Add(column);
  473. }
  474. return result;
  475. }
  476. }
  477. public override bool AddRemark(EntityInfo entity)
  478. {
  479. return true;
  480. }
  481. public override bool BackupTable(string oldTableName, string newTableName, int maxBackupDataRows = int.MaxValue)
  482. {
  483. oldTableName = this.SqlBuilder.GetTranslationTableName(oldTableName);
  484. newTableName = this.SqlBuilder.GetTranslationTableName(newTableName);
  485. string sql = string.Format(this.BackupTableSql, newTableName, oldTableName, maxBackupDataRows);
  486. this.Context.Ado.ExecuteCommand(sql);
  487. return true;
  488. }
  489. public override bool CreateTable(string tableName, List<DbColumnInfo> columns, bool isCreatePrimaryKey = true)
  490. {
  491. if (columns.HasValue())
  492. {
  493. foreach (var item in columns)
  494. {
  495. //if (item.DbColumnName.Equals("GUID", StringComparison.CurrentCultureIgnoreCase))
  496. //{
  497. // item.Length = 20;
  498. //}
  499. if (item.IsIdentity && !item.IsPrimarykey)
  500. {
  501. Check.Exception(true, "Identity only primary key");
  502. }
  503. }
  504. }
  505. string sql = GetCreateTableSql(tableName, columns);
  506. string primaryKeyInfo = null;
  507. if (!isCreatePrimaryKey || columns.Count(it => it.IsPrimarykey) > 1)
  508. {
  509. sql = sql.Replace("PRIMARY KEY AUTOINCREMENT", "").Replace("PRIMARY KEY", "");
  510. }
  511. if (columns.Count(it => it.IsPrimarykey) > 1 && isCreatePrimaryKey)
  512. {
  513. primaryKeyInfo = string.Format(",\r\n Primary key({0})", string.Join(",", columns.Where(it => it.IsPrimarykey).Select(it => this.SqlBuilder.GetTranslationColumnName(it.DbColumnName))));
  514. primaryKeyInfo = primaryKeyInfo.Replace("`", "\"");
  515. }
  516. sql = sql.Replace("$PrimaryKey", primaryKeyInfo);
  517. this.Context.Ado.ExecuteCommand(sql);
  518. return true;
  519. }
  520. protected override string GetCreateTableSql(string tableName, List<DbColumnInfo> columns)
  521. {
  522. List<string> columnArray = new List<string>();
  523. Check.Exception(columns.IsNullOrEmpty(), "No columns found ");
  524. foreach (var item in columns)
  525. {
  526. string columnName = item.DbColumnName;
  527. string dataType = item.DataType;
  528. if (dataType == "varchar" && item.Length == 0)
  529. {
  530. item.Length = 1;
  531. }
  532. string dataSize = item.Length > 0 ? string.Format("({0})", item.Length) : null;
  533. string nullType = item.IsNullable ? this.CreateTableNull : CreateTableNotNull;
  534. string primaryKey = item.IsPrimarykey ? this.CreateTablePirmaryKey : null;
  535. string identity = item.IsIdentity ? this.CreateTableIdentity : null;
  536. string addItem = string.Format(this.CreateTableColumn, this.SqlBuilder.GetTranslationColumnName(columnName), dataType, dataSize, nullType, primaryKey, identity);
  537. if (item.DefaultValue.HasValue()&&this.Context.CurrentConnectionConfig?.MoreSettings?.SqliteCodeFirstEnableDefaultValue == true)
  538. {
  539. var value = item.DefaultValue;
  540. if (!value.Contains("(")&&!value.EqualCase( "CURRENT_TIMESTAMP")&&!value.StartsWith("'"))
  541. {
  542. value = value.ToSqlValue();
  543. }
  544. addItem = $"{addItem} DEFAULT {value}";
  545. }
  546. if (item.ColumnDescription.HasValue() && this.Context.CurrentConnectionConfig?.MoreSettings?.SqliteCodeFirstEnableDescription == true)
  547. {
  548. addItem = $"{addItem} /*{item.ColumnDescription.Replace("\r","").Replace("\n", "")}*/ ";
  549. }
  550. columnArray.Add(addItem);
  551. }
  552. string tableString = string.Format(this.CreateTableSql, this.SqlBuilder.GetTranslationTableName(tableName), string.Join(",\r\n", columnArray));
  553. tableString = tableString.Replace("`", "\"");
  554. return tableString;
  555. }
  556. public override bool IsAnyConstraint(string constraintName)
  557. {
  558. throw new NotSupportedException("MySql IsAnyConstraint NotSupportedException");
  559. }
  560. public override bool BackupDataBase(string databaseName, string fullFileName)
  561. {
  562. this.Context.Ado.ExecuteCommand($"PRAGMA main.page_size=1024; PRAGMA main.locking_mode=EXCLUSIVE; PRAGMA main.cache_size=5000; PRAGMA main.synchronous=NORMAL; PRAGMA main.journal_mode=WAL; VACUUM INTO '{fullFileName.ToSqlFilter()}'");
  563. return false;
  564. }
  565. //private List<T> GetListOrCache<T>(string cacheKey, string sql)
  566. //{
  567. // return this.Context.Utilities.GetReflectionInoCacheInstance().GetOrCreate(cacheKey,
  568. // () =>
  569. // {
  570. // var isEnableLogEvent = this.Context.Ado.IsEnableLogEvent;
  571. // this.Context.Ado.IsEnableLogEvent = false;
  572. // var reval = this.Context.Ado.SqlQuery<T>(sql);
  573. // this.Context.Ado.IsEnableLogEvent = isEnableLogEvent;
  574. // return reval;
  575. // });
  576. //}
  577. #endregion
  578. }
  579. }