You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

908 lines
34 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #if MYSQL_6_9 || MYSQL_6_10
  2. /* 2021.11.07 */
  3. using Externals.MySql.Data.MySqlClient;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Data;
  7. using System.Net;
  8. using System.Text;
  9. using System.Transactions;
  10. using static Apewer.Source.SourceUtility;
  11. namespace Apewer.Source
  12. {
  13. /// <summary></summary>
  14. public class MySql : IDbClient
  15. {
  16. #region 基础
  17. private Timeout _timeout = null;
  18. private string _connectionstring = null;
  19. /// <summary>获取或设置日志记录。</summary>
  20. public Logger Logger { get; set; }
  21. /// <summary>超时设定。</summary>
  22. public Timeout Timeout { get => _timeout; }
  23. /// <summary>创建实例。</summary>
  24. public MySql(string connnectionString, Timeout timeout = default)
  25. {
  26. _connectionstring = connnectionString;
  27. _timeout = timeout ?? Timeout.Default;
  28. }
  29. /// <summary>获取当前的 MySqlConnection 对象。</summary>
  30. public IDbConnection Connection { get => _connection; }
  31. /// <summary>构建连接字符串以创建实例。</summary>
  32. public MySql(string address, string store, string user, string pass, Timeout timeout = null)
  33. {
  34. _timeout = timeout ?? Timeout.Default;
  35. var a = address ?? "";
  36. var s = store ?? "";
  37. var u = user ?? "";
  38. var p = pass ?? "";
  39. var cs = $"server={a}; database={s}; uid={u}; pwd={p}; ";
  40. _connectionstring = cs;
  41. _storename = new Class<string>(s);
  42. }
  43. #endregion
  44. #region 日志。
  45. private void LogError(string action, Exception ex, string addtion)
  46. {
  47. var logger = Logger;
  48. if (logger != null) logger.Error(this, "MySQL", action, ex.GetType().FullName, ex.Message, addtion);
  49. }
  50. #endregion
  51. #region Connection
  52. private MySqlConnection _connection = null;
  53. /// <summary></summary>
  54. public bool Online { get => _connection == null ? false : (_connection.State == ConnectionState.Open); }
  55. /// <summary>连接字符串。</summary>
  56. public string ConnectionString { get => _connectionstring; }
  57. /// <summary></summary>
  58. public string Connect()
  59. {
  60. if (_connection == null)
  61. {
  62. _connection = new MySqlConnection();
  63. _connection.ConnectionString = _connectionstring;
  64. }
  65. else
  66. {
  67. if (_connection.State == ConnectionState.Open) return null;
  68. }
  69. // try
  70. {
  71. _connection.Open();
  72. switch (_connection.State)
  73. {
  74. case ConnectionState.Open: return null;
  75. default: return $"连接失败,当前处于 {_connection.State} 状态。";
  76. }
  77. }
  78. // catch (Exception ex)
  79. // {
  80. // LogError("Connection", ex, _connection.ConnectionString);
  81. // Close();
  82. // return false;
  83. // }
  84. }
  85. /// <summary></summary>
  86. public void Close()
  87. {
  88. if (_connection != null)
  89. {
  90. if (_transaction != null)
  91. {
  92. if (_autocommit) Commit();
  93. else Rollback();
  94. }
  95. _connection.Close();
  96. _connection.Dispose();
  97. _connection = null;
  98. }
  99. }
  100. /// <summary></summary>
  101. public void Dispose() { Close(); }
  102. #endregion
  103. #region Transaction
  104. private IDbTransaction _transaction = null;
  105. private bool _autocommit = false;
  106. /// <summary>启动事务。</summary>
  107. public string Begin(bool commit = true) => Begin(commit, null);
  108. /// <summary>启动事务。</summary>
  109. public string Begin(bool commit, Class<System.Data.IsolationLevel> isolation)
  110. {
  111. if (Connect() != null) return "未连接。";
  112. if (_transaction != null) return "存在已启动的事务,无法再次启动。";
  113. try
  114. {
  115. _transaction = isolation ? _connection.BeginTransaction(isolation.Value) : _connection.BeginTransaction();
  116. _autocommit = commit;
  117. return null;
  118. }
  119. catch (Exception ex)
  120. {
  121. Logger.Error(nameof(MySql), "Commit", ex.Message());
  122. return ex.Message();
  123. }
  124. }
  125. /// <summary>提交事务。</summary>
  126. public string Commit()
  127. {
  128. if (_transaction == null) return "事务不存在。";
  129. try
  130. {
  131. _transaction.Commit();
  132. RuntimeUtility.Dispose(_transaction);
  133. _transaction = null;
  134. return null;
  135. }
  136. catch (Exception ex)
  137. {
  138. RuntimeUtility.Dispose(_transaction);
  139. _transaction = null;
  140. Logger.Error(nameof(MySql), "Commit", ex.Message());
  141. return ex.Message();
  142. }
  143. }
  144. /// <summary>从挂起状态回滚事务。</summary>
  145. public string Rollback()
  146. {
  147. if (_transaction == null) return "事务不存在。";
  148. try
  149. {
  150. _transaction.Rollback();
  151. RuntimeUtility.Dispose(_transaction);
  152. _transaction = null;
  153. return null;
  154. }
  155. catch (Exception ex)
  156. {
  157. RuntimeUtility.Dispose(_transaction);
  158. _transaction = null;
  159. Logger.Error(nameof(MySql), "Rollback", ex.Message());
  160. return ex.Message();
  161. }
  162. }
  163. #endregion
  164. #region SQL
  165. /// <summary></summary>
  166. public IQuery Query(string sql, IEnumerable<IDataParameter> parameters)
  167. {
  168. if (sql.IsBlank()) return Example.InvalidQueryStatement;
  169. var connected = Connect();
  170. if (connected.NotEmpty()) return Example.InvalidQueryConnection;
  171. try
  172. {
  173. using (var command = new MySqlCommand())
  174. {
  175. command.Connection = _connection;
  176. command.CommandTimeout = _timeout.Query;
  177. command.CommandText = sql;
  178. if (parameters != null)
  179. {
  180. foreach (var p in parameters)
  181. {
  182. if (p != null) command.Parameters.Add(p);
  183. }
  184. }
  185. using (var ds = new DataSet())
  186. {
  187. using (var da = new MySqlDataAdapter(command))
  188. {
  189. var name = "table_" + Guid.NewGuid().ToString("n");
  190. da.Fill(ds, name);
  191. var table = ds.Tables[name];
  192. return new Query(table);
  193. }
  194. }
  195. }
  196. }
  197. catch (Exception exception)
  198. {
  199. Logger.Error(nameof(MySql), "Query", exception, sql);
  200. return new Query(exception);
  201. }
  202. }
  203. /// <summary></summary>
  204. public IExecute Execute(string sql, IEnumerable<IDataParameter> parameters)
  205. {
  206. if (sql.IsBlank()) return Example.InvalidExecuteStatement;
  207. var connected = Connect();
  208. if (connected.NotEmpty()) return new Execute(false, connected);
  209. var inTransaction = _transaction != null;
  210. if (!inTransaction) Begin();
  211. try
  212. {
  213. using (var command = new MySqlCommand())
  214. {
  215. command.Connection = _connection;
  216. command.Transaction = (MySqlTransaction)_transaction;
  217. command.CommandTimeout = _timeout.Execute;
  218. command.CommandText = sql;
  219. if (parameters != null)
  220. {
  221. foreach (var parameter in parameters)
  222. {
  223. if (parameter == null) continue;
  224. command.Parameters.Add(parameter);
  225. }
  226. }
  227. var rows = command.ExecuteNonQuery();
  228. if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。
  229. return new Execute(true, rows);
  230. }
  231. }
  232. catch (Exception exception)
  233. {
  234. Logger.Error(nameof(MySql), "Execute", exception, sql);
  235. if (!inTransaction) Rollback();
  236. return new Execute(exception);
  237. }
  238. }
  239. /// <summary></summary>
  240. public IQuery Query(string sql) => Query(sql, null);
  241. /// <summary></summary>
  242. public IExecute Execute(string sql, IEnumerable<Parameter> parameters)
  243. {
  244. var dps = null as List<IDataParameter>;
  245. if (parameters != null)
  246. {
  247. var count = parameters.Count();
  248. dps = new List<IDataParameter>(count);
  249. foreach (var p in parameters)
  250. {
  251. var dp = Parameter(p);
  252. dps.Add(dp);
  253. }
  254. }
  255. return Execute(sql, dps);
  256. }
  257. /// <summary></summary>
  258. public IExecute Execute(string sql) => Execute(sql, null as IEnumerable<IDataParameter>);
  259. #endregion
  260. #region ORM
  261. private Class<string> _storename = null;
  262. private string StoreName()
  263. {
  264. if (_storename) return _storename.Value;
  265. _storename = new Class<string>(Internals.TextHelper.ParseConnectionString(_connectionstring).GetValue("database"));
  266. return _storename.Value ?? "";
  267. }
  268. private string[] FirstColumn(string sql)
  269. {
  270. using (var query = Query(sql) as Query) return query.ReadColumn();
  271. }
  272. /// <summary></summary>
  273. public string[] TableNames()
  274. {
  275. var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", StoreName(), "' and table_type='base table';");
  276. return FirstColumn(sql);
  277. }
  278. /// <summary></summary>
  279. public string[] ViewNames()
  280. {
  281. var sql = TextUtility.Merge("select table_name from information_schema.tables where table_schema='", StoreName(), "' and table_type='view';");
  282. return FirstColumn(sql);
  283. }
  284. /// <summary></summary>
  285. public string[] ColumnNames(string table)
  286. {
  287. var sql = TextUtility.Merge("select column_name from information_schema.columns where table_schema='", StoreName(), "' and table_name='", TextUtility.AntiInject(table), "';");
  288. return FirstColumn(sql);
  289. }
  290. /// <summary>获取用于创建表的语句。</summary>
  291. private string GetCreateStetement(TableStructure structure)
  292. {
  293. // 检查现存表。
  294. var exists = false;
  295. var tables = TableNames();
  296. if (tables.Length > 0)
  297. {
  298. var lower = structure.Name.ToLower();
  299. foreach (var table in tables)
  300. {
  301. if (TextUtility.IsBlank(table)) continue;
  302. if (table.ToLower() == lower)
  303. {
  304. exists = true;
  305. break;
  306. }
  307. }
  308. }
  309. if (exists)
  310. {
  311. var columns = ColumnNames(structure.Name);
  312. if (columns.Length > 0)
  313. {
  314. var lower = new List<string>(columns.Length);
  315. var added = 0;
  316. foreach (var column in columns)
  317. {
  318. if (TextUtility.IsBlank(column)) continue;
  319. lower.Add(column.ToLower());
  320. added++;
  321. }
  322. lower.Capacity = added;
  323. columns = lower.ToArray();
  324. }
  325. var sqlsb = new StringBuilder();
  326. foreach (var column in structure.Columns)
  327. {
  328. // 检查 Independent 特性。
  329. if (structure.Independent && column.Independent) continue;
  330. // 去重。
  331. var lower = column.Field.ToLower();
  332. if (columns.Contains(lower)) continue;
  333. var type = Declaration(column);
  334. if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
  335. // alter table `_record` add column `_index` bigint;
  336. sqlsb.Append("alter table `", structure.Name, "` add column ", type, "; ");
  337. }
  338. var sql = sqlsb.ToString();
  339. return sql;
  340. }
  341. else
  342. {
  343. // create table _record (`_index` bigint, `_key` varchar(255), `_text` longtext) engine=innodb default charset=utf8mb4
  344. var columns = new List<string>(structure.Columns.Length);
  345. var columnsAdded = 0;
  346. var primarykeys = new List<string>();
  347. foreach (var column in structure.Columns)
  348. {
  349. // 检查 Independent 特性。
  350. if (structure.Independent && column.Independent) continue;
  351. // 字段。
  352. var type = Declaration(column);
  353. if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
  354. if (!column.Independent)
  355. {
  356. if (column.PrimaryKey) primarykeys.Add(column.Field);
  357. if (column.Incremental) type += " auto_increment";
  358. }
  359. columns.Add(type);
  360. columnsAdded++;
  361. }
  362. columns.Capacity = columnsAdded;
  363. var table = structure.Name;
  364. var joined = string.Join(", ", columns);
  365. // 设置主键。
  366. string sql;
  367. var sqlPrimaryKey = primarykeys.Count < 1 ? "" : (", primary key (" + string.Join(", ", primarykeys.ToArray()) + ")");
  368. sql = TextUtility.Merge("create table `", table, "`(", joined, sqlPrimaryKey, ") engine=innodb default charset=utf8mb4; ");
  369. return sql;
  370. }
  371. }
  372. /// <summary></summary>
  373. private string Initialize(Type model, out string sql)
  374. {
  375. if (model == null)
  376. {
  377. sql = null;
  378. return "指定的类型无效。";
  379. }
  380. var structure = TableStructure.Parse(model);
  381. if (structure == null)
  382. {
  383. sql = null;
  384. return "无法解析记录模型。";
  385. }
  386. // 连接数据库。
  387. var connect = Connect();
  388. if (connect.NotEmpty())
  389. {
  390. sql = null;
  391. return $"连接数据库失败。({connect})";
  392. }
  393. sql = GetCreateStetement(structure);
  394. if (sql.NotEmpty())
  395. {
  396. var execute = Execute(sql);
  397. if (!execute.Success) return execute.Message;
  398. }
  399. return null;
  400. }
  401. /// <summary></summary>
  402. public string Initialize(Type model) => Initialize(model, out string sql);
  403. /// <summary></summary>
  404. public string Initialize<T>() where T : class, new() => Initialize(typeof(T));
  405. /// <summary></summary>
  406. public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType());
  407. /// <summary>插入记录。返回错误信息。</summary>
  408. public string Insert(object record, string table = null)
  409. {
  410. if (record == null) return "参数无效。";
  411. SourceUtility.FixProperties(record);
  412. var structure = TableStructure.Parse(record.GetType());
  413. if (structure == null) return "无法解析记录模型。";
  414. if (string.IsNullOrEmpty(table)) table = structure.Name;
  415. if (string.IsNullOrEmpty(table)) return "表名称无效。";
  416. // 排除字段。
  417. var excluded = new List<string>();
  418. foreach (var ca in structure.Columns)
  419. {
  420. // 排除不使用 ORM 的属性。
  421. if (ca.Independent || ca.Incremental) excluded.Add(ca.Field);
  422. }
  423. var ps = structure.CreateParameters(record, Parameter, excluded);
  424. var psc = ps.Length;
  425. if (psc < 1) return "数据模型不包含字段。";
  426. var names = new List<string>(psc);
  427. var values = new List<string>(psc);
  428. foreach (var p in ps)
  429. {
  430. var pn = p.ParameterName;
  431. names.Add("`" + p + "`");
  432. values.Add("@" + p);
  433. }
  434. var ns = string.Join(", ", names);
  435. var vs = string.Join(", ", values);
  436. var sql = $"insert into `{table}` ({ns}) values ({vs}); ";
  437. var execute = Execute(sql, ps);
  438. if (execute.Success) return TextUtility.Empty;
  439. return execute.Message;
  440. }
  441. /// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary>
  442. /// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks>
  443. public string Update(IRecord record, string table = null)
  444. {
  445. if (record == null) return "参数无效。";
  446. FixProperties(record);
  447. SetUpdated(record);
  448. var structure = TableStructure.Parse(record.GetType());
  449. if (structure == null) return "无法解析记录模型。";
  450. if (structure.Independent) return "无法更新带有 Independent 特性的模型。";
  451. if (string.IsNullOrEmpty(table)) table = structure.Name;
  452. if (string.IsNullOrEmpty(table)) return "表名称无效。";
  453. // 排除字段。
  454. var excluded = new List<string>();
  455. if (structure.Key != null) excluded.Add(structure.Key.Field);
  456. foreach (var ca in structure.Columns)
  457. {
  458. // 排除不使用 ORM 的属性、自增属性和主键属性。
  459. if (ca.Independent || ca.Incremental || ca.PrimaryKey) excluded.Add(ca.Field);
  460. }
  461. var ps = structure.CreateParameters(record, Parameter, excluded);
  462. var psc = ps.Length;
  463. if (psc < 1) return "数据模型不包含字段。";
  464. var items = new List<string>(psc);
  465. foreach (var p in ps)
  466. {
  467. var pn = p.ParameterName;
  468. items.Add(TextUtility.Merge("`", pn, "` = @", pn));
  469. }
  470. var key = record.Key.SafeKey();
  471. var sql = $"update `{table}` set {string.Join(", ", items)} where `_key`='{key}'; ";
  472. var execute = Execute(sql, ps);
  473. if (execute.Success) return TextUtility.Empty;
  474. return execute.Message;
  475. }
  476. /// <summary></summary>
  477. public Result<object[]> Query(Type model, string sql, IEnumerable<IDataParameter> parameters = null) => SourceUtility.Query(this, model, sql, parameters);
  478. /// <summary></summary>
  479. public Result<T[]> Query<T>(string sql, IEnumerable<IDataParameter> parameters = null) where T : class, new()
  480. {
  481. var query = Query(sql, parameters);
  482. if (!query.Success) return new Result<T[]>(query.Message);
  483. var records = query.Fill<T>();
  484. query.Dispose();
  485. var result = new Result<T[]>(records);
  486. return result;
  487. }
  488. /// <summary>获取所有记录。Flag 为 0 时将忽略 Flag 条件。</summary>
  489. public Result<object[]> Query(Type model, long flag = 0) => SourceUtility.Query(this, model, (tn) =>
  490. {
  491. if (flag == 0) return $"select * from `{tn}`; ";
  492. return $"select * from `{tn}` where `_flag`={flag}; ";
  493. });
  494. /// <summary>获取所有记录。Flag 为 0 时将忽略 Flag 条件。</summary>
  495. public Result<T[]> Query<T>(long flag = 0) where T : class, IRecord, new() => SourceUtility.Query<T>(this, (tn) =>
  496. {
  497. if (flag == 0) return $"select * from `{tn}`; ";
  498. return $"select * from `{tn}` where `_flag`={flag}; ";
  499. });
  500. /// <summary>获取记录。</summary>
  501. /// <param name="model">填充的记录模型。</param>
  502. /// <param name="skip">要跳过的记录数,可用最小值为 0。</param>
  503. /// <param name="count">要获取的记录数,可用最小值为 1。</param>
  504. public Result<object[]> Query(Type model, int skip, int count)
  505. {
  506. if (skip < 0) return new Result<object[]>("参数 skip 超出了范围。");
  507. if (count < 1) return new Result<object[]>("参数 count 超出了范围。");
  508. return SourceUtility.Query(this, model, (tn) => $"select * from `{tn}` limit {skip}, {count}; ");
  509. }
  510. /// <summary>获取记录。</summary>
  511. /// <param name="skip">要跳过的记录数,可用最小值为 0。</param>
  512. /// <param name="count">要获取的记录数,可用最小值为 1。</param>
  513. public Result<T[]> Query<T>(int skip, int count) where T : class, IRecord, new()
  514. {
  515. if (skip < 0) return new Result<T[]>("参数 skip 超出了范围。");
  516. if (count < 1) return new Result<T[]>("参数 count 超出了范围。");
  517. return SourceUtility.Query<T>(this, (tn) => $"select * from `{tn}` limit {skip}, {count}; ");
  518. }
  519. /// <summary>获取记录。</summary>
  520. public Result<object> Get(Type model, string key, long flag = 0) => SourceUtility.Get(this, model, key, (tn, sk) =>
  521. {
  522. if (flag == 0) return $"select * from `{tn}` where `_key`='{sk}' limit 1;";
  523. return $"select * from `{tn}` where `_key`='{sk}' and `_flag`={flag} limit 1;";
  524. });
  525. /// <summary>获取记录。</summary>
  526. public Result<T> Get<T>(string key, long flag = 0) where T : class, IRecord, new() => SourceUtility.Get<T>(this, key, (tn, sk) =>
  527. {
  528. if (flag == 0) return $"select * from `{tn}` where `_key`='{sk}' limit 1;";
  529. return $"select * from `{tn}` where `_key`='{sk}' and `_flag`={flag} limit 1;";
  530. });
  531. /// <summary>>获取指定类型的主键,按 Flag 属性筛选。</summary>
  532. public Result<string[]> Keys(Type model, long flag = 0) => SourceUtility.Keys(this, model, (tn) =>
  533. {
  534. if (flag == 0) return $"select `_key` from `{tn}`;";
  535. return $"select `_key` from `{tn}` where `_flag`={flag};";
  536. });
  537. /// <summary>>获取指定类型的主键,按 Flag 属性筛选。</summary>
  538. public Result<string[]> Keys<T>(long flag = 0) where T : class, IRecord, new() => Keys(typeof(T), flag);
  539. /// <summary>对表添加列,返回错误信息。</summary>
  540. /// <typeparam name="T">记录类型。</typeparam>
  541. /// <param name="column">列名称。</param>
  542. /// <param name="type">字段类型。</param>
  543. /// <param name="length">字段长度,仅对 VarChar 和 NVarChar 类型有效。</param>
  544. /// <returns></returns>
  545. public string AddColumn<T>(string column, ColumnType type, int length = 0) where T : class, IRecord
  546. {
  547. var columnName = SafeColumn(column);
  548. if (columnName.IsEmpty()) return "列名无效。";
  549. var ta = TableAttribute.Parse(typeof(T));
  550. if (ta == null) return "无法解析记录模型。";
  551. var tableName = ta.Name;
  552. var columeType = "";
  553. switch (type)
  554. {
  555. case ColumnType.Integer:
  556. columeType = "bigint";
  557. break;
  558. case ColumnType.Float:
  559. columeType = "double";
  560. break;
  561. case ColumnType.Bytes:
  562. columeType = "longblob";
  563. break;
  564. case ColumnType.DateTime:
  565. columeType = "datetime";
  566. break;
  567. case ColumnType.VarChar:
  568. case ColumnType.NVarChar:
  569. columeType = $"varchar({length})";
  570. break;
  571. case ColumnType.VarChar191:
  572. case ColumnType.NVarChar191:
  573. columeType = "varchar(191)";
  574. break;
  575. case ColumnType.VarCharMax:
  576. case ColumnType.NVarCharMax:
  577. columeType = "varchar(max)";
  578. break;
  579. case ColumnType.Text:
  580. columeType = "longtext";
  581. break;
  582. }
  583. if (columeType.IsEmpty()) return "类型不支持。";
  584. var sql = $"alter table `{tableName}` add {columnName} {columeType}; ";
  585. var execute = Execute(sql) as Execute;
  586. var error = execute.Message;
  587. return error;
  588. }
  589. #endregion
  590. #region static
  591. /// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
  592. public static string Escape(string text, int bytes = 0)
  593. {
  594. if (text.IsEmpty()) return "";
  595. var t = text ?? "";
  596. t = t.Replace("\\", "\\\\");
  597. t = t.Replace("'", "\\'");
  598. t = t.Replace("\n", "\\n");
  599. t = t.Replace("\r", "\\r");
  600. t = t.Replace("\b", "\\b");
  601. t = t.Replace("\t", "\\t");
  602. t = t.Replace("\f", "\\f");
  603. if (bytes > 5)
  604. {
  605. if (t.Bytes(Encoding.UTF8).Length > bytes)
  606. {
  607. while (true)
  608. {
  609. t = t.Substring(0, t.Length - 1);
  610. if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break;
  611. }
  612. t = t + " ...";
  613. }
  614. }
  615. return t;
  616. }
  617. /// <summary></summary>
  618. public static string SafeTable(string table)
  619. {
  620. const string chars = "0123456789_-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  621. var safety = TextUtility.Restrict(table, chars).Lower();
  622. var pc = 0;
  623. for (var i = 0; i < safety.Length; i++)
  624. {
  625. if (safety[i] == '-') pc += 1;
  626. else break;
  627. }
  628. if (pc == safety.Length) return "";
  629. if (pc > 0) safety = safety.Substring(pc);
  630. return safety;
  631. }
  632. /// <summary></summary>
  633. public static string SafeColumn(string column) => SafeTable(column);
  634. /// <summary></summary>
  635. /// <exception cref="ArgumentNullException"></exception>
  636. /// <exception cref="InvalidOperationException"></exception>
  637. internal static MySqlParameter Parameter(Parameter parameter)
  638. {
  639. if (parameter == null) throw new InvalidOperationException("参数无效。");
  640. return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value);
  641. }
  642. /// <summary></summary>
  643. internal static MySqlParameter Parameter(string name, ColumnType type, Int32 size, object value)
  644. {
  645. if (TextUtility.IsBlank(name)) return null;
  646. var dbtype = MySqlDbType.Int64;
  647. switch (type)
  648. {
  649. case ColumnType.Bytes:
  650. dbtype = MySqlDbType.LongBlob;
  651. break;
  652. case ColumnType.Integer:
  653. dbtype = MySqlDbType.Int64;
  654. break;
  655. case ColumnType.Float:
  656. dbtype = MySqlDbType.Double;
  657. break;
  658. case ColumnType.DateTime:
  659. dbtype = MySqlDbType.DateTime;
  660. break;
  661. case ColumnType.VarChar:
  662. case ColumnType.VarChar191:
  663. case ColumnType.VarCharMax:
  664. case ColumnType.NVarChar:
  665. case ColumnType.NVarChar191:
  666. case ColumnType.NVarCharMax:
  667. dbtype = MySqlDbType.VarChar;
  668. break;
  669. case ColumnType.Text:
  670. case ColumnType.NText:
  671. dbtype = MySqlDbType.LongText;
  672. break;
  673. default:
  674. throw new InvalidOperationException(TextUtility.Merge("类型 ", type.ToString(), " 不受支持。"));
  675. }
  676. switch (type)
  677. {
  678. case ColumnType.VarChar:
  679. case ColumnType.NVarChar:
  680. size = NumberUtility.Restrict(size, 0, 65535);
  681. break;
  682. case ColumnType.VarChar191:
  683. case ColumnType.NVarChar191:
  684. size = NumberUtility.Restrict(size, 0, 191);
  685. break;
  686. default:
  687. size = 0;
  688. break;
  689. }
  690. if (value is string && value != null && size > 0)
  691. {
  692. value = TextUtility.Left((string)value, size);
  693. }
  694. var parameter = new MySqlParameter();
  695. parameter.ParameterName = name;
  696. parameter.MySqlDbType = dbtype;
  697. parameter.Value = value;
  698. if (size > 0) parameter.Size = size;
  699. return parameter;
  700. }
  701. /// <summary></summary>
  702. internal static MySqlParameter Parameter(string name, MySqlDbType type, object value, Int32 size = 0)
  703. {
  704. var parameter = new MySqlParameter();
  705. parameter.ParameterName = name;
  706. parameter.MySqlDbType = type;
  707. parameter.Value = value;
  708. if (size > 0) parameter.Size = size;
  709. return parameter;
  710. }
  711. private static string Declaration(ColumnAttribute column)
  712. {
  713. var type = TextUtility.Empty;
  714. var length = Math.Max(0, (int)column.Length);
  715. switch (column.Type)
  716. {
  717. case ColumnType.Integer:
  718. type = "bigint";
  719. break;
  720. case ColumnType.Float:
  721. type = "double";
  722. break;
  723. case ColumnType.Bytes:
  724. type = "longblob";
  725. break;
  726. case ColumnType.DateTime:
  727. type = "datetime";
  728. break;
  729. case ColumnType.VarChar:
  730. type = TextUtility.Merge("varchar(", Math.Max(65535, length).ToString(), ")");
  731. break;
  732. case ColumnType.VarChar191:
  733. type = TextUtility.Merge("varchar(191)");
  734. break;
  735. case ColumnType.VarCharMax:
  736. type = TextUtility.Merge("varchar(max)");
  737. break;
  738. case ColumnType.Text:
  739. type = TextUtility.Merge("longtext");
  740. break;
  741. case ColumnType.NVarChar:
  742. type = TextUtility.Merge("varchar(", Math.Min(65535, length).ToString(), ")");
  743. break;
  744. case ColumnType.NVarChar191:
  745. type = TextUtility.Merge("varchar(191)");
  746. break;
  747. case ColumnType.NVarCharMax:
  748. type = TextUtility.Merge("varchar(65535)");
  749. break;
  750. case ColumnType.NText:
  751. type = TextUtility.Merge("longtext");
  752. break;
  753. default:
  754. return TextUtility.Empty;
  755. }
  756. return TextUtility.Merge("`", (string)column.Field, "` ", type);
  757. }
  758. /// <summary>获取每个数据库中,每个表的容量,单位为字节。</summary>
  759. public static Dictionary<string, Dictionary<string, long>> GetTablesCapacity(MySql source)
  760. {
  761. var result = new Dictionary<string, Dictionary<string, long>>();
  762. if (source != null && source.Connect().IsEmpty())
  763. {
  764. var sql = "select `table_schema`, `table_name`, `engine`, `data_length`, `index_length` from `information_schema`.tables order by `table_schema`, `table_name`";
  765. using (var query = (Query)source.Query(sql))
  766. {
  767. for (var r = 0; r < query.Rows; r++)
  768. {
  769. var store = query.Text(r, "table_schema");
  770. var table = query.Text(r, "table_name");
  771. var engine = query.Text(r, "engine");
  772. var capacity = NumberUtility.Int64(query.Text(r, "data_length")) + NumberUtility.Int64(query.Text(r, "index_length"));
  773. if (store == "mysql") continue;
  774. if (store == "information_schema") continue;
  775. if (store == "performance_schema") continue;
  776. if (engine != "MyISAM" && engine != "InnoDB") continue;
  777. if (!result.ContainsKey(store)) result.Add(store, new Dictionary<string, long>());
  778. if (!result[store].ContainsKey(table)) result[store].Add(table, 0L);
  779. result[store][table] = capacity;
  780. }
  781. }
  782. }
  783. return result;
  784. }
  785. #endregion
  786. }
  787. }
  788. #endif