PageRenderTime 82ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/VisualStudio/DocumentView/Documents/TableDocument.cs

https://github.com/rykr/connector-net
C# | 3833 lines | 2280 code | 469 blank | 1084 comment | 498 complexity | 551511a65d50c195c0939af085b2b403 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, MPL-2.0-no-copyleft-exception
  1. // Copyright (C) 2006-2007 MySQL AB
  2. //
  3. // This file is part of MySQL Tools for Visual Studio.
  4. // MySQL Tools for Visual Studio is free software; you can redistribute it
  5. // and/or modify it under the terms of the GNU Lesser General Public
  6. // License version 2.1 as published by the Free Software Foundation
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU Lesser General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU Lesser General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System;
  16. /*
  17. * This file contains implementation of data object for TABLE representation.
  18. */
  19. using System;
  20. using System.ComponentModel;
  21. using System.Collections.Generic;
  22. using System.Diagnostics;
  23. using System.Text;
  24. using System.Data;
  25. using Microsoft.VisualStudio.Data;
  26. using Microsoft.VisualStudio.Shell;
  27. using Microsoft.VisualStudio.Shell.Interop;
  28. using Microsoft.VisualStudio;
  29. using MySql.Data.VisualStudio.Utils;
  30. using System.Windows.Forms.Design;
  31. using System.Data.SqlTypes;
  32. using MySql.Data.VisualStudio.Descriptors;
  33. using Table = MySql.Data.VisualStudio.Descriptors.TableDescriptor.Attributes;
  34. using Column = MySql.Data.VisualStudio.Descriptors.ColumnDescriptor.Attributes;
  35. using ForeignKey = MySql.Data.VisualStudio.Descriptors.ForeignKeyDescriptor.Attributes;
  36. using ForeignKeyColumn = MySql.Data.VisualStudio.Descriptors.ForeignKeyColumnDescriptor.Attributes;
  37. using Index = MySql.Data.VisualStudio.Descriptors.IndexDescriptor.Attributes;
  38. using IndexColumn = MySql.Data.VisualStudio.Descriptors.IndexColumnDescriptor.Attributes;
  39. using System.Globalization;
  40. using MySql.Data.VisualStudio.Properties;
  41. using System.Drawing.Design;
  42. using System.Windows.Forms;
  43. namespace MySql.Data.VisualStudio.DocumentView
  44. {
  45. /// <summary>
  46. /// This class implements document (data object) functionality and
  47. /// represent database table. It implements IVsPersistDocData interface and
  48. /// beaves like filelles data object for Visual Studio enviroment.
  49. /// </summary>
  50. [DocumentObject(TableDescriptor.TypeName, typeof(TableDocument))]
  51. public class TableDocument : BaseDocument, CollationConverter.ICharacterSetProvider
  52. {
  53. #region Initialization
  54. /// <summary>
  55. /// This constructor initialize private identifier variables.
  56. /// </summary>
  57. /// <param name="hierarchy">
  58. /// Data view hierarchy accessor used to interact with Server Explorer.
  59. /// Also used to extract connection.
  60. /// </param>
  61. /// <param name="id">
  62. /// Array with the object identifier.
  63. /// </param>
  64. /// <param name="isNew">
  65. /// Indicates if this instance represents new database object doesn’t fixed in
  66. /// database yet.
  67. /// </param>
  68. public TableDocument(ServerExplorerFacade hierarchy, bool isNew, object[] id)
  69. :base(hierarchy, isNew, id)
  70. {
  71. }
  72. #endregion
  73. #region BaseDocument overridings
  74. /// <summary>
  75. /// Dirty flag used to determine whenever data are changed or not. At this
  76. /// point table data considered as changed if Columns data table has changes.
  77. /// </summary>
  78. protected override bool IsDirty
  79. {
  80. get
  81. {
  82. // Asks base class
  83. if (base.IsDirty)
  84. return true;
  85. Debug.Assert(Columns != null, "Collumns are not read!");
  86. Debug.Assert(ForeignKeys != null, "Foreign keys are not read!");
  87. Debug.Assert(ForeignKeysColumns != null, "Foreign keys columns are not read!");
  88. Debug.Assert(Indexes != null, "Indexes are not read!");
  89. Debug.Assert(IndexColumns != null, "IndexColumns columns are not read!");
  90. // If we aren't read return true
  91. if (Columns == null || ForeignKeys == null || ForeignKeysColumns == null
  92. || Indexes == null || IndexColumns == null)
  93. return false;
  94. // Check column list and other tables
  95. if (DataInterpreter.HasChanged(Columns)
  96. || DataInterpreter.HasChanged(ForeignKeys)
  97. || DataInterpreter.HasChanged(ForeignKeysColumns)
  98. || DataInterpreter.HasChanged(Indexes)
  99. || DataInterpreter.HasChanged(IndexColumns))
  100. return true; ;
  101. return false;
  102. }
  103. }
  104. /// <summary>
  105. /// Returns query for pre-dropping foreign keys and indexes.
  106. /// </summary>
  107. /// <returns>Returns query for pre-dropping foreign keys and indexes.</returns>
  108. protected override string BuildPreDropQuery()
  109. {
  110. // Pre-drop foreign keys and indexes if needed
  111. if (NeedToDropForeignKeys())
  112. return BuildPreDropForeignKeys();
  113. return String.Empty;
  114. }
  115. /// <summary>
  116. /// Builds alter query for table.
  117. /// </summary>
  118. /// <returns>Alter query for table.</returns>
  119. protected override string BuildAlterQuery()
  120. {
  121. StringBuilder query = new StringBuilder();
  122. // Write header
  123. BuildAlterHeader(query);
  124. BuildAlterSpecifications(query);
  125. return query.ToString();
  126. }
  127. /// <summary>
  128. /// Builda create query for table.
  129. /// </summary>
  130. /// <returns>Create query for table.</returns>
  131. protected override string BuildCreateQuery()
  132. {
  133. StringBuilder query = new StringBuilder();
  134. BuildCreateHeader(query);
  135. query.Append(" (");
  136. BuildCreateDefinition(query);
  137. query.Append(" )");
  138. BuildTableOptions(query, null);
  139. return query.ToString();
  140. }
  141. /// <summary>
  142. /// Load database object from database.
  143. /// </summary>
  144. /// <param name="reloading">
  145. /// This flag indicates that object is reloading. Should be ignored in most cases.
  146. /// </param>
  147. /// <returns>Returns true if load succeeds and false otherwise.</returns>
  148. protected override bool LoadData(bool reloading)
  149. {
  150. // Read table atributes using base method
  151. if (!base.LoadData(reloading))
  152. return false;
  153. // Reset current columns table, if any.
  154. if (columnsTable != null)
  155. ResetColumnsTable();
  156. // Reset foreign keys table, if any
  157. if (foreignKeysTable != null)
  158. ResetForeignKeysTable();
  159. // Reset foreign keys columns table, if any
  160. if (foreignKeysColumnsTable != null)
  161. ResetForeignKeysColumnsTable();
  162. // Reset indexes table, if any
  163. if (indexesTable != null)
  164. ResetIndexesTable();
  165. // Reset indexes columns table, if any
  166. if (indexesTable != null)
  167. ResetIndexesColumnsTable();
  168. // Read columns for table
  169. columnsTable = ColumnDescriptor.Enumerate(Connection, ObjectIDForLoad);
  170. if (columnsTable == null)
  171. {
  172. Debug.Fail("Failed to read columns!");
  173. return false;
  174. }
  175. // Subscribe to new columns table events
  176. SubscribeToColumnsTableEvents();
  177. // Read foreign keys for table.
  178. foreignKeysTable = ForeignKeyDescriptor.Enumerate(Connection, ObjectIDForLoad);
  179. if (foreignKeysTable == null)
  180. {
  181. Debug.Fail("Failed to read foreign keys!");
  182. return false;
  183. }
  184. // Subscrube to events
  185. SubscribeToForeignKeysTableEvents();
  186. // Read foreign keys columns for table.
  187. foreignKeysColumnsTable = ForeignKeyColumnDescriptor.Enumerate(Connection, ObjectIDForLoad);
  188. if (foreignKeysColumnsTable == null)
  189. {
  190. Debug.Fail("Failed to read foreign keys columns!");
  191. return false;
  192. }
  193. // Subscrube to events
  194. SubscribeToForeignKeysColumnsTableEvents();
  195. // Read indexes for table.
  196. indexesTable = IndexDescriptor.Enumerate(Connection, ObjectIDForLoad);
  197. if (indexesTable == null)
  198. {
  199. Debug.Fail("Failed to read indexes!");
  200. return false;
  201. }
  202. // Subscrube to events
  203. SubscribeToIndexesTableEvents();
  204. // Read index columns for table.
  205. indexColumnsTable = IndexColumnDescriptor.Enumerate(Connection, ObjectIDForLoad);
  206. if (indexColumnsTable == null)
  207. {
  208. Debug.Fail("Failed to read index columns!");
  209. return false;
  210. }
  211. // Subscrube to events
  212. SubscribeToIndexesColumnsTableEvents();
  213. return true;
  214. }
  215. /// <summary>
  216. /// Accepts changes in column grid.
  217. /// </summary>
  218. protected override void AcceptChanges()
  219. {
  220. base.AcceptChanges();
  221. // Accepting columns changes
  222. columnsTable.AcceptChanges();
  223. }
  224. /// <summary>
  225. /// Fills aditional properties for new table.
  226. /// </summary>
  227. /// <param name="newRow">DataRow to fill with properties.</param>
  228. protected override void FillNewObjectAttributes(DataRow newRow)
  229. {
  230. base.FillNewObjectAttributes(newRow);
  231. newRow[Table.Engine] = Connection.DefaultEngine;
  232. newRow[Table.CharacterSet] = Connection.DefaultCharacterSet;
  233. newRow[Table.Collation] = Connection.DefaultCollation;
  234. }
  235. /// <summary>
  236. /// Resets data for the cloned table.
  237. /// </summary>
  238. protected override void ResetToNew()
  239. {
  240. // Call to base
  241. base.ResetToNew();
  242. // Validate columns and other data (must be loaded)
  243. if (Columns == null || ForeignKeys == null || ForeignKeysColumns == null
  244. || Indexes == null || IndexColumns == null)
  245. return;
  246. //Reset columns
  247. DataTable temp = MakeCopy(Columns);
  248. ResetTableName(temp, Column.Table);
  249. ResetColumnsTable();
  250. columnsTable = temp;
  251. SubscribeToColumnsTableEvents();
  252. //Reset foreign keys
  253. temp = MakeCopy(ForeignKeys);
  254. ResetTableName(temp, ForeignKey.Table);
  255. // For each foreign key we need new name (some times InnoDB falls if keys have same name
  256. // even in the different tables)
  257. foreach (DataRow key in temp.Rows)
  258. GenerateNewName(key);
  259. ResetForeignKeysTable();
  260. foreignKeysTable = temp;
  261. SubscribeToForeignKeysTableEvents();
  262. //Reset foreign key columns
  263. temp = MakeCopy(ForeignKeysColumns);
  264. ResetTableName(temp, ForeignKeyColumn.Table);
  265. ResetForeignKeysColumnsTable();
  266. foreignKeysColumnsTable = temp;
  267. SubscribeToForeignKeysColumnsTableEvents();
  268. //Reset indexes
  269. temp = MakeCopy(Indexes);
  270. ResetTableName(temp, Index.Table);
  271. ResetIndexesTable();
  272. indexesTable = temp;
  273. SubscribeToIndexesTableEvents();
  274. //Reset indexes columns
  275. temp = MakeCopy(IndexColumns);
  276. ResetTableName(temp, IndexColumn.Table);
  277. ResetIndexesColumnsTable();
  278. indexColumnsTable = temp;
  279. SubscribeToIndexesColumnsTableEvents();
  280. }
  281. /// <summary>
  282. /// Processes failures on save. May remove pre-dropped foreign keys and indexes
  283. /// from tables.
  284. /// </summary>
  285. protected override void SaveFailed()
  286. {
  287. base.SaveFailed();
  288. // If query was with two parts and at first part we droped foreign keys
  289. // we must check all keys and indexes for existens
  290. if (!NeedToDropForeignKeys())
  291. {
  292. // Call to base
  293. CallBaseSaveFailed();
  294. return;
  295. }
  296. // Flag to indicate that we droped somthing
  297. bool dropped = false;
  298. // Enumerate current foreign keys
  299. DataTable currentKeys = ForeignKeyDescriptor.Enumerate(Connection, OldObjectID);
  300. Debug.Assert(currentKeys != null && currentKeys.Rows != null, "Failed to re-enumerate foreign keys");
  301. if (currentKeys != null && currentKeys.Rows != null)
  302. {
  303. foreach (DataRow key in ForeignKeys.Select())
  304. {
  305. switch (key.RowState)
  306. {
  307. // Skip added and deleted keys
  308. case DataRowState.Added:
  309. case DataRowState.Deleted:
  310. break;
  311. // Check if this key was changed and pre-droped
  312. default:
  313. if (HasForeignKeyChanged(key))
  314. {
  315. DataRow currentKey = currentKeys.Rows.Find(new object[] {
  316. Schema,
  317. OldName,
  318. DataInterpreter.GetString(key, ForeignKey.Name) });
  319. // If no current key founded, mark it as new
  320. if (currentKey == null)
  321. {
  322. dropped = true;
  323. key.AcceptChanges();
  324. key.SetAdded();
  325. }
  326. }
  327. break;
  328. }
  329. }
  330. }
  331. // Enumerate current indexes
  332. DataTable currentIndexes = IndexDescriptor.Enumerate(Connection, OldObjectID);
  333. Debug.Assert(currentIndexes != null && currentIndexes.Rows != null, "Failed to re-enumerate indexes");
  334. if (currentIndexes != null && currentIndexes.Rows != null)
  335. {
  336. foreach (DataRow index in Indexes.Select())
  337. {
  338. switch (index.RowState)
  339. {
  340. // Skip added and deleted indexes
  341. case DataRowState.Added:
  342. case DataRowState.Deleted:
  343. break;
  344. // Check if this index was changed and pre-droped
  345. default:
  346. DataRow currentIndex = currentIndexes.Rows.Find(new object[] {
  347. Schema,
  348. OldName,
  349. DataInterpreter.GetString(index, Index.Name) });
  350. // If no current index founded, mark it as new
  351. if (currentIndex == null)
  352. {
  353. dropped = true;
  354. index.AcceptChanges();
  355. index.SetAdded();
  356. }
  357. break;
  358. }
  359. }
  360. }
  361. // Call to base
  362. CallBaseSaveFailed();
  363. // Warn user if key or index was pre-droped
  364. if (dropped)
  365. UIHelper.ShowWarning(Resources.Warning_KeyWasPredropped);
  366. }
  367. #endregion
  368. #region Index changes handling
  369. /// <summary>
  370. /// Unsubscribe from old indexes table event
  371. /// </summary>
  372. private void ResetIndexesTable()
  373. {
  374. indexesTable.TableNewRow -= new DataTableNewRowEventHandler(OnNewIndex);
  375. indexesTable.RowChanged -= new DataRowChangeEventHandler(OnIndexRowChanged);
  376. indexesTable.RowDeleted -= new DataRowChangeEventHandler(OnIndexRowDeleted);
  377. indexesTable.Dispose();
  378. indexesTable = null;
  379. }
  380. /// <summary>
  381. /// Subscribe to new index table events
  382. /// </summary>
  383. private void SubscribeToIndexesTableEvents()
  384. {
  385. indexesTable.TableNewRow += new DataTableNewRowEventHandler(OnNewIndex);
  386. indexesTable.RowChanged += new DataRowChangeEventHandler(OnIndexRowChanged);
  387. indexesTable.RowDeleted += new DataRowChangeEventHandler(OnIndexRowDeleted);
  388. }
  389. /// <summary>
  390. /// Handles chnages for the index rows.
  391. /// </summary>
  392. /// <param name="sender">Event sender, unused.</param>
  393. /// <param name="e">Detailed information about event</param>>
  394. void OnIndexRowChanged(object sender, DataRowChangeEventArgs e)
  395. {
  396. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  397. // Extract changed index
  398. DataRow index = e.Row;
  399. if (index == null)
  400. return;
  401. // Chose right method for action
  402. switch (e.Action)
  403. {
  404. case DataRowAction.Change:
  405. HandleIndexChanges(index);
  406. break;
  407. default: break;
  408. }
  409. }
  410. /// <summary>
  411. /// Handles adding new index.
  412. /// </summary>
  413. /// <param name="sender">Event sender, unused.</param>
  414. /// <param name="e">Detailed information about event.</param>
  415. void OnNewIndex(object sender, DataTableNewRowEventArgs e)
  416. {
  417. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  418. // Extract added index
  419. DataRow newIndex = e.Row;
  420. if (newIndex == null)
  421. return;
  422. // Initialize new column attributes
  423. newIndex[Index.Schema] = Schema;
  424. newIndex[Index.Table] = OldName; // Old name is used to keep table name for all indexes the same.
  425. newIndex[Index.Name] = BuildNewIndexName();
  426. newIndex[Index.IndexKind] = IndexDescriptor.INDEX;
  427. newIndex[Index.IndexType] = IndexDescriptor.BTREE;
  428. }
  429. /// <summary>
  430. /// If index changed, ensures that all index columns will have right index name.
  431. /// </summary>
  432. /// <param name="index">DataRow with index data.</param>
  433. private void HandleIndexChanges(DataRow index)
  434. {
  435. string newName = DataInterpreter.GetStringNotNull(index, Index.Name);
  436. // Iterate through index columns
  437. string indexName;
  438. foreach (DataRow column in IndexColumns.Rows)
  439. {
  440. // Need to skip deleted columns if any
  441. if (column.RowState == DataRowState.Deleted)
  442. continue;
  443. // Extract index name
  444. indexName = DataInterpreter.GetStringNotNull(column, IndexColumn.Index);
  445. // Check if any index with this name (search for index with given schema, table and name)
  446. DataRow candidate = FindIndex(indexName);
  447. // If index was not found, need to change index name for index column.
  448. if (candidate == null)
  449. column[IndexColumn.Index] = newName;
  450. }
  451. }
  452. /// <summary>
  453. /// Handles indeex deletion and delete all index columns.
  454. /// </summary>
  455. /// <param name="sender">Event sender, unused.</param>
  456. /// <param name="e">Detailed information about event</param>>
  457. private void OnIndexRowDeleted(object sender, DataRowChangeEventArgs e)
  458. {
  459. string indexName;
  460. foreach (DataRow column in IndexColumns.Select())
  461. {
  462. // Need to skip deleted columns if any
  463. if (column.RowState == DataRowState.Deleted)
  464. continue;
  465. // Extract index name
  466. indexName = DataInterpreter.GetStringNotNull(column, IndexColumn.Index);
  467. // Check if any index with this name (search for index with given schema, table and name)
  468. DataRow candidate = FindIndex(indexName);
  469. // If index was not found, need to delete index column.
  470. if (candidate == null)
  471. column.Delete();
  472. }
  473. }
  474. /// <summary>
  475. /// Builds name for the new new index in format Index_N.
  476. /// </summary>
  477. /// <returns>Returns name for the new new index in format Index_N.</returns>
  478. private string BuildNewIndexName()
  479. {
  480. // Initialize search data
  481. int count = 0; string result; DataRow[] existsIndexes = null;
  482. // Generate new index name
  483. do
  484. {
  485. result = String.Format(CultureInfo.CurrentCulture, Resources.New_Index_Template, ++count);
  486. existsIndexes = DataInterpreter.Select(Indexes, Index.Name, result);
  487. }
  488. while (existsIndexes == null || existsIndexes.Length > 0);
  489. // Return results
  490. return result;
  491. }
  492. #endregion
  493. #region Indexes columns changes handling
  494. /// <summary>
  495. /// Unsubscribe from old indexes columns table event
  496. /// </summary>
  497. private void ResetIndexesColumnsTable()
  498. {
  499. indexColumnsTable.TableNewRow -= new DataTableNewRowEventHandler(OnNewIndexColumn);
  500. indexColumnsTable.RowChanged -= new DataRowChangeEventHandler(OnIndexColumnChanged);
  501. indexColumnsTable.RowDeleted -= new DataRowChangeEventHandler(OnIndexColumnChanged);
  502. indexColumnsTable.Dispose();
  503. indexColumnsTable = null;
  504. }
  505. /// <summary>
  506. /// Subscribe to new indexes columns table events
  507. /// </summary>
  508. private void SubscribeToIndexesColumnsTableEvents()
  509. {
  510. indexColumnsTable.TableNewRow += new DataTableNewRowEventHandler(OnNewIndexColumn);
  511. indexColumnsTable.RowChanged += new DataRowChangeEventHandler(OnIndexColumnChanged);
  512. indexColumnsTable.RowDeleted += new DataRowChangeEventHandler(OnIndexColumnChanged);
  513. }
  514. /// <summary>
  515. /// Handles adding new indes collumn.
  516. /// </summary>
  517. /// <param name="sender">Event sender, unused.</param>
  518. /// <param name="e">Detailed information about event.</param>
  519. void OnNewIndexColumn(object sender, DataTableNewRowEventArgs e)
  520. {
  521. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  522. // Extract added index column
  523. DataRow newColumn = e.Row;
  524. if (newColumn == null)
  525. return;
  526. // Initialize new column attributes
  527. newColumn[IndexColumn.Schema] = Schema;
  528. newColumn[IndexColumn.Table] = OldName; // Old name is used to keep table name for all columns the same.
  529. // Set index name to empty string
  530. newColumn[IndexColumn.Index] = String.Empty;
  531. // Search for any not deleted column and set name for index column
  532. DataRow dataColumn = DataInterpreter.GetNotDeletedRow(Columns);
  533. newColumn[IndexColumn.Name] = dataColumn != null
  534. ? DataInterpreter.GetStringNotNull(dataColumn, Column.Name)
  535. : String.Empty;
  536. }
  537. /// <summary>
  538. /// Handles index column changes and deletions. Adjust table columns primary key status.
  539. /// </summary>
  540. /// <param name="sender">Event sender, unused.</param>
  541. /// <param name="e">Detailed information about event.</param>
  542. void OnIndexColumnChanged(object sender, DataRowChangeEventArgs e)
  543. {
  544. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  545. // Extract added column
  546. DataRow column = e.Row;
  547. if (column == null)
  548. return;
  549. // Chose right method for action
  550. switch (e.Action)
  551. {
  552. case DataRowAction.Add:
  553. case DataRowAction.Change:
  554. HandleIndexColumnChanges(column);
  555. break;
  556. case DataRowAction.Delete:
  557. HandleIndexColumnDeleted();
  558. break;
  559. default: break;
  560. }
  561. }
  562. /// <summary>
  563. /// Then primary key index column is deleted, ensures that table column will be unmarked
  564. /// as primary key.
  565. /// </summary>
  566. private void HandleIndexColumnDeleted()
  567. {
  568. // Check each primary key column
  569. foreach (DataRow column in DataInterpreter.Select(Columns, Column.IsPrimaryKey, DataInterpreter.True))
  570. {
  571. // Skip deleted columns
  572. if (column.RowState == DataRowState.Deleted)
  573. continue;
  574. // Ensure that index column stil exists
  575. DataRow indexColumn = FindIndexColumn(IndexDescriptor.PRIMARY, DataInterpreter.GetStringNotNull(column, Column.Name));
  576. if(indexColumn == null)
  577. DataInterpreter.SetValueIfChanged(column, Column.IsPrimaryKey, DataInterpreter.False);
  578. }
  579. }
  580. /// <summary>
  581. /// Handles index column changes and mark/unmark primary key columns.
  582. /// </summary>
  583. /// <param name="column">DataRow with index column data.</param>
  584. private void HandleIndexColumnChanges(DataRow column)
  585. {
  586. // Extract index name
  587. string indexName = DataInterpreter.GetStringNotNull(column, IndexColumn.Index);
  588. // Extract related table column
  589. DataRow tableColumn = FindColumn(DataInterpreter.GetStringNotNull(column, IndexColumn.Name));
  590. if (tableColumn == null)
  591. return;
  592. // If index is primary key, mark column as primary key
  593. if (DataInterpreter.CompareInvariant(indexName, IndexDescriptor.PRIMARY))
  594. DataInterpreter.SetValueIfChanged(tableColumn, Column.IsPrimaryKey, DataInterpreter.True);
  595. }
  596. #endregion
  597. #region Foreign keys changes handling
  598. /// <summary>
  599. /// Unsubscribe from old foreign keys columns table event
  600. /// </summary>
  601. private void ResetForeignKeysTable()
  602. {
  603. foreignKeysTable.TableNewRow -= new DataTableNewRowEventHandler(OnNewForeignKey);
  604. foreignKeysTable.RowChanged -= new DataRowChangeEventHandler(OnForeignKeyRowChanged);
  605. foreignKeysTable.RowDeleted -= new DataRowChangeEventHandler(OnForeignKeyRowDeleted);
  606. foreignKeysTable.Dispose();
  607. foreignKeysTable = null;
  608. }
  609. /// <summary>
  610. /// Subscribe to new foreign keys columns table events
  611. /// </summary>
  612. private void SubscribeToForeignKeysTableEvents()
  613. {
  614. foreignKeysTable.TableNewRow += new DataTableNewRowEventHandler(OnNewForeignKey);
  615. foreignKeysTable.RowChanged += new DataRowChangeEventHandler(OnForeignKeyRowChanged);
  616. foreignKeysTable.RowDeleted += new DataRowChangeEventHandler(OnForeignKeyRowDeleted);
  617. }
  618. /// <summary>
  619. /// Handles chnages for the column rows.
  620. /// </summary>
  621. /// <param name="sender">Event sender, unused.</param>
  622. /// <param name="e">Detailed information about event</param>>
  623. void OnForeignKeyRowChanged(object sender, DataRowChangeEventArgs e)
  624. {
  625. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  626. // Extract changed key
  627. DataRow key = e.Row;
  628. if (key == null)
  629. return;
  630. // Chose right method for action
  631. switch (e.Action)
  632. {
  633. case DataRowAction.Change:
  634. HandleKeyChanges(key);
  635. break;
  636. default: break;
  637. }
  638. }
  639. /// <summary>
  640. /// Handles adding new key.
  641. /// </summary>
  642. /// <param name="sender">Event sender, unused.</param>
  643. /// <param name="e">Detailed information about event.</param>
  644. void OnNewForeignKey(object sender, DataTableNewRowEventArgs e)
  645. {
  646. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  647. // Extract added foreign key column
  648. DataRow newKey = e.Row;
  649. if (newKey == null)
  650. return;
  651. // Initialize new column attributes
  652. newKey[ForeignKey.Schema] = Schema;
  653. newKey[ForeignKey.Table] = OldName; // Old name is used to keep table name for all keys the same.
  654. newKey[ForeignKey.Name] = BuildNewKeyName();
  655. newKey[ForeignKey.OnDelete] = ForeignKeyDescriptor.RESTRICT;
  656. newKey[ForeignKey.OnUpdate] = ForeignKeyDescriptor.RESTRICT;
  657. }
  658. /// <summary>
  659. /// If foreign key changed, ensures that all foreign key columns will have right
  660. /// key name.
  661. /// </summary>
  662. /// <param name="key">DataRow with foreign key data.</param>
  663. private void HandleKeyChanges(DataRow key)
  664. {
  665. string newName = DataInterpreter.GetStringNotNull(key, ForeignKey.Name);
  666. // Iterate through foreign keys columns
  667. string fkName;
  668. foreach (DataRow column in ForeignKeysColumns.Rows)
  669. {
  670. // Need to skip deleted columns if any
  671. if (column.RowState == DataRowState.Deleted)
  672. continue;
  673. // Extract foreign key name
  674. fkName = DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.ForeignKeyName);
  675. // Check if any foreign key with this name (search for key with given schema, table and name)
  676. DataRow candidate = FindForeignKey(fkName);
  677. // If key was not found, need to change key name for foreign key column.
  678. if (candidate == null)
  679. column[ForeignKeyColumn.ForeignKeyName] = newName;
  680. }
  681. }
  682. /// <summary>
  683. /// Handles foreign key deletion and delete all foreign key columns.
  684. /// </summary>
  685. /// <param name="sender">Event sender, unused.</param>
  686. /// <param name="e">Detailed information about event</param>>
  687. private void OnForeignKeyRowDeleted(object sender, DataRowChangeEventArgs e)
  688. {
  689. string fkName;
  690. foreach (DataRow column in ForeignKeysColumns.Select())
  691. {
  692. // Need to skip deleted columns if any
  693. if (column.RowState == DataRowState.Deleted)
  694. continue;
  695. // Extract foreign key name
  696. fkName = DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.ForeignKeyName);
  697. // Check if any foreign key with this name (search for key with given schema, table and name)
  698. DataRow candidate = FindForeignKey(fkName);
  699. // If key was not found, need to delete foreign key column.
  700. if (candidate == null)
  701. column.Delete();
  702. }
  703. }
  704. /// <summary>
  705. /// Builds name for the new foreign key in format FK_{table name}_N.
  706. /// </summary>
  707. /// <returns>Returns name for the new foreign key in format FK_{table name}_N.</returns>
  708. private string BuildNewKeyName()
  709. {
  710. // Initialize searche data
  711. int count = 0; string result; DataRow[] existsKeys = null;
  712. // Generate new column name
  713. do
  714. {
  715. result = String.Format(CultureInfo.CurrentCulture, Resources.New_ForeignKey_Template, Name, ++count);
  716. existsKeys = DataInterpreter.Select(ForeignKeys, ForeignKey.Name, result);
  717. }
  718. while (existsKeys == null || existsKeys.Length > 0);
  719. // Return results
  720. return result;
  721. }
  722. #endregion
  723. #region Foreign keys column changes handling
  724. /// <summary>
  725. /// Unsubscribe from old foreign keys columns table event
  726. /// </summary>
  727. private void ResetForeignKeysColumnsTable()
  728. {
  729. foreignKeysColumnsTable.TableNewRow -= new DataTableNewRowEventHandler(OnNewForeignKeyColumn);
  730. foreignKeysColumnsTable.Dispose();
  731. foreignKeysColumnsTable = null;
  732. }
  733. /// <summary>
  734. /// Subscribe to new foreign keys columns table events
  735. /// </summary>
  736. private void SubscribeToForeignKeysColumnsTableEvents()
  737. {
  738. foreignKeysColumnsTable.TableNewRow += new DataTableNewRowEventHandler(OnNewForeignKeyColumn);
  739. }
  740. /// <summary>
  741. /// Handles adding new foreign key collumn.
  742. /// </summary>
  743. /// <param name="sender">Event sender, unused.</param>
  744. /// <param name="e">Detailed information about event.</param>
  745. void OnNewForeignKeyColumn(object sender, DataTableNewRowEventArgs e)
  746. {
  747. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  748. // Extract added foreign key column
  749. DataRow newColumn = e.Row;
  750. if (newColumn == null)
  751. return;
  752. // Initialize new column attributes
  753. newColumn[ForeignKeyColumn.Schema] = Schema;
  754. newColumn[ForeignKeyColumn.Table] = OldName; // Old name is used to keep table name for all columns the same.
  755. // Set foreign key name to empty string
  756. newColumn[ForeignKeyColumn.ForeignKeyName] = String.Empty;
  757. // Search for any not deleted column and set name for foreign key column
  758. DataRow dataColumn = DataInterpreter.GetNotDeletedRow(Columns);
  759. newColumn[ForeignKeyColumn.Name] = dataColumn != null
  760. ? DataInterpreter.GetStringNotNull(dataColumn, Column.Name)
  761. : String.Empty;
  762. }
  763. #endregion
  764. #region Columns changes handling
  765. /// <summary>
  766. /// Unsubscribe from old columns table event
  767. /// </summary>
  768. private void ResetColumnsTable()
  769. {
  770. columnsTable.TableNewRow -= new DataTableNewRowEventHandler(OnNewColumnRow);
  771. columnsTable.RowChanged -= new DataRowChangeEventHandler(OnColumnRowChanged);
  772. columnsTable.RowDeleted -= new DataRowChangeEventHandler(OnColumnDeleted);
  773. columnsTable.Dispose();
  774. columnsTable = null;
  775. // Clear column defaults dictionary
  776. columnDefaults.Clear();
  777. }
  778. /// <summary>
  779. /// Subscribe to new columns table events
  780. /// </summary>
  781. private void SubscribeToColumnsTableEvents()
  782. {
  783. columnsTable.TableNewRow += new DataTableNewRowEventHandler(OnNewColumnRow);
  784. columnsTable.RowChanged += new DataRowChangeEventHandler(OnColumnRowChanged);
  785. columnsTable.RowDeleted += new DataRowChangeEventHandler(OnColumnDeleted);
  786. // Add default value for each column into defaluts dictionary
  787. foreach (DataRow column in columnsTable.Rows)
  788. if (column != null && column.HasVersion(DataRowVersion.Current))
  789. columnDefaults[DataInterpreter.GetStringNotNull(column, Column.Name)]
  790. = DataInterpreter.GetString(column, Column.Default);
  791. }
  792. /// <summary>
  793. /// Handles chnages for the column rows.
  794. /// </summary>
  795. /// <param name="sender">Event sender, unused.</param>
  796. /// <param name="e">Detailed information about event</param>>
  797. void OnColumnRowChanged(object sender, DataRowChangeEventArgs e)
  798. {
  799. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  800. // Extract added column
  801. DataRow column = e.Row;
  802. if (column == null)
  803. return;
  804. // Chose right method for action
  805. switch (e.Action)
  806. {
  807. case DataRowAction.Add:
  808. CompleteNewCollumn(column);
  809. HandleColumnChanges(column);
  810. break;
  811. case DataRowAction.Change:
  812. HandleColumnChanges(column);
  813. break;
  814. default: break;
  815. }
  816. }
  817. /// <summary>
  818. /// Handles adding new collumn.
  819. /// </summary>
  820. /// <param name="sender">Event sender, unused.</param>
  821. /// <param name="e">Detailed information about event.</param>
  822. private void OnNewColumnRow(object sender, DataTableNewRowEventArgs e)
  823. {
  824. Debug.Assert(e != null && e.Row != null, "Empty event argumets provided!");
  825. // Extract added column
  826. DataRow newColumn = e.Row;
  827. if (newColumn == null)
  828. return;
  829. // Initialize new column attributes
  830. newColumn[Column.Schema] = Schema;
  831. newColumn[Column.Table] = OldName; // Old name is used to keep table name for all columns the same.
  832. newColumn[Column.Name] = BuildNewColumnName();
  833. }
  834. /// <summary>
  835. /// Handles chenges in the column attributes.
  836. /// </summary>
  837. /// <param name="column">Data row with column attributes.</param>
  838. private void HandleColumnChanges(DataRow column)
  839. {
  840. string newName = DataInterpreter.GetStringNotNull(column, Column.Name);
  841. // Iterate through foreign keys columns
  842. string fkName;
  843. foreach (DataRow fkColumn in ForeignKeysColumns.Rows)
  844. {
  845. // Need to skip deleted columns if any
  846. if (fkColumn.RowState == DataRowState.Deleted)
  847. continue;
  848. fkName = DataInterpreter.GetStringNotNull(fkColumn, ForeignKeyColumn.Name);
  849. // Check if any data column with proper name (search for columns with given schema, table and name)
  850. DataRow candidate = FindColumn(fkName);
  851. // If column was not found, need to change column name for foreign key column.
  852. if (candidate == null)
  853. fkColumn[ForeignKeyColumn.Name] = newName;
  854. }
  855. // Iterate through indexes columns
  856. string indexName;
  857. foreach (DataRow indexColumn in IndexColumns.Rows)
  858. {
  859. // Need to skip deleted columns if any
  860. if (indexColumn.RowState == DataRowState.Deleted)
  861. continue;
  862. indexName = DataInterpreter.GetStringNotNull(indexColumn, IndexColumn.Name);
  863. // Check if any data column with proper name (search for columns with given schema, table and name)
  864. DataRow candidate = FindColumn(indexName);
  865. // If column was not found, need to change column name for index column.
  866. if (candidate == null)
  867. indexColumn[IndexColumn.Name] = newName;
  868. }
  869. // If column is primary key, should be index column in index PRIMARY
  870. if (DataInterpreter.GetSqlBool(column, Column.IsPrimaryKey).IsTrue)
  871. IncludeInPrimaryKey(newName);
  872. // If column is not primary key, exclude it from primary key index
  873. if (DataInterpreter.GetSqlBool(column, Column.IsPrimaryKey).IsFalse)
  874. ExcludeFromPrimaryKey(newName);
  875. // If default value is null, reset nullable flag (only for not auto increment columns)
  876. if (!DataInterpreter.IsNotNull(column, Column.Default)
  877. && DataInterpreter.GetSqlBool(column, Column.IsAutoIncrement).IsFalse)
  878. {
  879. object oldDefault = null;
  880. // If previous default value is stored and it is not null, reset allow nulls flag
  881. if (columnDefaults.TryGetValue(DataInterpreter.GetStringNotNull(column, Column.Name), out oldDefault)
  882. && oldDefault != null)
  883. DataInterpreter.SetValueIfChanged(column, Column.Nullable, DataInterpreter.True);
  884. }
  885. // If not null flag set, reset default value to empty string (only for not auto increment columns)
  886. if (DataInterpreter.GetSqlBool(column, Column.Nullable).IsFalse
  887. && !DataInterpreter.IsNotNull(column, Column.Default)
  888. && DataInterpreter.GetSqlBool(column, Column.IsAutoIncrement).IsFalse)
  889. SetDefault(column);
  890. // Stores old default
  891. columnDefaults[DataInterpreter.GetStringNotNull(column, Column.Name)]
  892. = DataInterpreter.GetString(column, Column.Default);
  893. }
  894. /// <summary>
  895. /// Includes given column into primary key.
  896. /// </summary>
  897. /// <param name="newName">Name of column to include in primary key.</param>
  898. private void IncludeInPrimaryKey(string newName)
  899. {
  900. // Create primary key index if not created yet
  901. if (FindIndex(IndexDescriptor.PRIMARY) == null)
  902. {
  903. DataRow primaryKey = Indexes.NewRow();
  904. primaryKey[Index.Name] = IndexDescriptor.PRIMARY;
  905. primaryKey[Index.IndexKind] = IndexDescriptor.PRIMARY;
  906. Indexes.Rows.Add(primaryKey);
  907. }
  908. // Extract exists primary key columns
  909. DataRow[] columns = DataInterpreter.Select(
  910. IndexColumns,
  911. IndexColumn.Index,
  912. IndexDescriptor.PRIMARY);
  913. // Create primary key column if not exists
  914. if (FindIndexColumn(IndexDescriptor.PRIMARY, newName) == null)
  915. {
  916. DataRow pkColumn = IndexColumns.NewRow();
  917. pkColumn[IndexColumn.Name] = newName;
  918. pkColumn[IndexColumn.Index] = IndexDescriptor.PRIMARY;
  919. // Index ordinals are one-based
  920. pkColumn[IndexColumn.Ordinal] = GetMaximumOrdinal(columns);
  921. IndexColumns.Rows.Add(pkColumn);
  922. }
  923. }
  924. /// <summary>
  925. /// Excludes given column from primary key.
  926. /// </summary>
  927. /// <param name="newName">Name of column to exclude from primary key.</param>
  928. private void ExcludeFromPrimaryKey(string newName)
  929. {
  930. // Delete related primary key column
  931. DataRow pkColumn = FindIndexColumn(IndexDescriptor.PRIMARY, newName);
  932. if (pkColumn != null)
  933. pkColumn.Delete();
  934. // If no more primary key columns, delete primary key index
  935. DataRow[] pkColumns = DataInterpreter.Select(IndexColumns, IndexColumn.Index, IndexDescriptor.PRIMARY);
  936. if (pkColumns == null || pkColumns.Length <= 0)
  937. {
  938. // Delete primary key, if any
  939. DataRow primaryKey = FindIndex(IndexDescriptor.PRIMARY);
  940. if (primaryKey != null)
  941. primaryKey.Delete();
  942. }
  943. }
  944. /// <summary>
  945. /// Returns maximum ordinal value for the given index columns.
  946. /// </summary>
  947. /// <param name="columns">Array with index columns to process.</param>
  948. /// <returns>Returns maximum ordinal value for the given index columns.</returns>
  949. private static Int64 GetMaximumOrdinal(DataRow[] columns)
  950. {
  951. if (columns == null)
  952. return 1;
  953. Int64 result = columns.Length + 1;
  954. foreach (DataRow column in columns)
  955. if (DataInterpreter.GetInt(column, IndexColumn.Ordinal) >= result)
  956. result = (Int64)DataInterpreter.GetInt(column, IndexColumn.Ordinal) + 1;
  957. return result;
  958. }
  959. /// <summary>
  960. /// Completes definition of the command.
  961. /// </summary>
  962. /// <param name="newColumn">Command data row to complete.</param>
  963. private void CompleteNewCollumn(DataRow newColumn)
  964. {
  965. // Extract column name, if any
  966. string columnName = DataInterpreter.GetString(newColumn, Column.Name);
  967. // If column name is given and type is not, try to find out default options
  968. if (!String.IsNullOrEmpty(columnName)
  969. && !DataInterpreter.IsNotEmptyString(newColumn, Column.MySqlType))
  970. {
  971. // If column ends with ID, let it be integer, otherwise, let it be varchar
  972. if (columnName.EndsWith(Resources.ID_Column_Name, StringComparison.CurrentCulture))
  973. {
  974. newColumn[Column.MySqlType] = TableDescriptor.DefaultIntType;
  975. newColumn[Column.Unsigned] = DataInterpreter.True;
  976. }
  977. else
  978. newColumn[Column.MySqlType] = TableDescriptor.DefaultCharType;
  979. // If column is ID and table have not primary key yet, let column be primary key
  980. if (columnName.Equals(Resources.ID_Column_Name, StringComparison.CurrentCulture)
  981. && !HasPrimaryKey)
  982. {
  983. newColumn[Column.IsAutoIncrement] = DataInterpreter.True;
  984. newColumn[Column.IsPrimaryKey] = DataInterpreter.True;
  985. }
  986. else
  987. {
  988. // Set default value if columns is not marked as autoincrement
  989. SetDefault(newColumn);
  990. }
  991. // Let column be not nullabale
  992. newColumn[Column.Nullable] = DataInterpreter.False;
  993. }
  994. columnDefaults[DataInterpreter.GetStringNotNull(newColumn, Column.Name)]
  995. = DataInterpreter.GetString(newColumn, Column.Default);
  996. }
  997. /// <summary>
  998. /// Handles column deletion and deletes related foreign key columns.
  999. /// </summary>
  1000. /// <param name="sender">Event sender, unused.</param>
  1001. /// <param name="e">Detailed information about event.</param>
  1002. private void OnColumnDeleted(object sender, DataRowChangeEventArgs e)
  1003. {
  1004. // Iterate through foreign keys columns
  1005. string fkName;
  1006. foreach (DataRow fkColumn in ForeignKeysColumns.Select())
  1007. {
  1008. // Need to skip deleted columns if any
  1009. if (fkColumn.RowState == DataRowState.Deleted)
  1010. continue;
  1011. fkName = DataInterpreter.GetStringNotNull(fkColumn, ForeignKeyColumn.Name);
  1012. // Check if any data column with proper name (search for columns with given schema, table and name)
  1013. DataRow candidate = FindColumn(fkName);
  1014. // If column was not found, need to delete this foreign key column.
  1015. if (candidate == null)
  1016. fkColumn.Delete();
  1017. }
  1018. // Iterate through indexes columns
  1019. string indexName;
  1020. foreach (DataRow indexColumn in IndexColumns.Select())
  1021. {
  1022. // Need to skip deleted columns if any
  1023. if (indexColumn.RowState == DataRowState.Deleted)
  1024. continue;
  1025. indexName = DataInterpreter.GetStringNotNull(indexColumn, IndexColumn.Name);
  1026. // Check if any data column with proper name (search for columns with given schema, table and name)
  1027. DataRow candidate = FindColumn(indexName);
  1028. // If column was not found, need to delete this index column.
  1029. if (candidate == null)
  1030. indexColumn.Delete();
  1031. }
  1032. }
  1033. /// <summary>
  1034. /// Builds new name for the column.
  1035. /// </summary>
  1036. /// <returns>Returns new name for the column.</returns>
  1037. private string BuildNewColumnName()
  1038. {
  1039. // Initialize searche data
  1040. int count = 0; string result; DataRow[] existsColumns = null;
  1041. // Generate new column name
  1042. do
  1043. {
  1044. result = String.Format(CultureInfo.CurrentCulture, Resources.New_Column_Name_Template, ++count);
  1045. existsColumns = DataInterpreter.Select(Columns, Column.Name, result);
  1046. }
  1047. while (existsColumns == null || existsColumns.Length > 0);
  1048. // Return results
  1049. return result;
  1050. }
  1051. /// <summary>
  1052. /// Set not empty default value for the column.
  1053. /// </summary>
  1054. /// <param name="column">Column, for which default value should be set.</param>
  1055. private void SetDefault(DataRow column)
  1056. {
  1057. if (Parser.IsNumericType(DataInterpreter.GetStringNotNull(column, Column.MySqlType)))
  1058. DataInterpreter.SetValueIfChanged(column, Column.Default, 0);
  1059. else
  1060. DataInterpreter.SetValueIfChanged(column, Column.Default, String.Empty);
  1061. }
  1062. #endregion
  1063. #region Data validation before save
  1064. /// <summary>
  1065. /// Validates table settings in complex.
  1066. /// </summary>
  1067. /// <returns>
  1068. /// Returns true if table is consistent and can be saved.
  1069. /// Returns false otherwize.
  1070. /// </returns>
  1071. protected override bool ValidateData()
  1072. {
  1073. // Fires saving event by calling base class
  1074. if (!base.ValidateData())
  1075. return false;
  1076. // Validate columns and other data (must be loaded)
  1077. if (Columns == null || ForeignKeys == null || ForeignKeysColumns == null
  1078. || Indexes == null || IndexColumns == null)
  1079. return false;
  1080. // Validate table options
  1081. if (!ValidateOptions())
  1082. return false;
  1083. // Validate column names and types
  1084. if (!ValidateColumns())
  1085. return false;
  1086. // Validates primary keys (should not allow nulls)
  1087. if (!ValidatePrimaryKeys())
  1088. return false;
  1089. // Validates auto increment columns
  1090. if (!ValidateAutoIncrementColumns())
  1091. return false;
  1092. // Validates foreign keys
  1093. if (!ValidateForeignKeys())
  1094. return false;
  1095. // Validates foreign keys
  1096. if (!ValidateIndexes())
  1097. return false;
  1098. return true;
  1099. }
  1100. /// <summary>
  1101. /// Returns false if any table option is invalid.
  1102. /// </summary>
  1103. /// <returns>Returns false if any table option is invalid.</returns>
  1104. private bool ValidateOptions()
  1105. {
  1106. // Once set, UNION can not be reset
  1107. if (DataInterpreter.HasChanged(Attributes, Table.Union)
  1108. && !DataInterpreter.IsNotEmptyString(Attributes, Table.Union))
  1109. {
  1110. UIHelper.ShowError(String.Format(
  1111. CultureInfo.CurrentCulture,
  1112. Resources.Error_CantResetUnion,
  1113. Name));
  1114. return false;
  1115. }
  1116. // TODO: Add other options validation
  1117. return true;
  1118. }
  1119. /// <summary>
  1120. /// Returns false if any column has invalid name or invalid type.
  1121. /// </summary>
  1122. /// <returns>Returns false if any column has invalid name or invalid type.</returns>
  1123. private bool ValidateColumns()
  1124. {
  1125. // Columns should exists
  1126. if (Columns.Rows.Count <= 0)
  1127. {
  1128. UIHelper.ShowError(String.Format(
  1129. Resources.Error_NoColumns,
  1130. Name));
  1131. return false;
  1132. }
  1133. // Iterate through columns
  1134. foreach (DataRow column in Columns.Rows)
  1135. {
  1136. if (column == null || column.RowState == DataRowState.Deleted)
  1137. continue;
  1138. // Extract name and check for emptines
  1139. string name = DataInterpreter.GetString(column, Column.Name);
  1140. if (String.IsNullOrEmpty(name))
  1141. {
  1142. UIHelper.ShowError(String.Format(
  1143. Resources.Error_EmptyColumnName,
  1144. Name));
  1145. return false;
  1146. }
  1147. // Validate indentifier
  1148. if (!Parser.IsValidIdentifier(name))
  1149. {
  1150. UIHelper.ShowError(String.Format(
  1151. Resources.Error_InvalidColumnName,
  1152. name,
  1153. Name));
  1154. return false;
  1155. }
  1156. // Validate column type
  1157. string datatype = DataInterpreter.GetStringNotNull(column, Column.MySqlType);
  1158. if (!Parser.IsValidDatatype(datatype))
  1159. {
  1160. UIHelper.ShowError(String.Format(
  1161. Resources.Error_InvlaidColumnType,
  1162. datatype,
  1163. name,
  1164. Name));
  1165. return false;
  1166. }
  1167. // TODO: Check for supported by engine types
  1168. // BLOB and TEXT columns should not have default values
  1169. if ((Parser.IsBlobType(datatype) || Parser.IsTextType(datatype))
  1170. && DataInterpreter.IsNotEmptyString(column, Column.Default))
  1171. {
  1172. UIHelper.ShowError(String.Format(
  1173. Resources.Error_DefaultForBlobOrText,
  1174. datatype,
  1175. name,
  1176. Name));
  1177. return false;
  1178. }
  1179. }
  1180. // If everything is ok, return true
  1181. return true;
  1182. }
  1183. /// <summary>
  1184. /// Checks if there any nulable primary key and reset it with
  1185. /// proper warning.
  1186. /// </summary>
  1187. private bool ValidatePrimaryKeys()
  1188. {
  1189. // Select all primary keys
  1190. DataRow[] pk = DataInterpreter.Select(Columns, Column.IsPrimaryKey, DataInterpreter.True);
  1191. // If primary keys was not founded, return true
  1192. if (pk == null || pk.Length == 0)
  1193. return true;
  1194. // Build list of primary keys which allow nulls
  1195. StringBuilder list = new StringBuilder();
  1196. foreach (DataRow column in pk)
  1197. {
  1198. // Check row
  1199. if(column == null)
  1200. {
  1201. Debug.Fail("Null column in array!");
  1202. continue;
  1203. }
  1204. // If not nulable, continue
  1205. if (DataInterpreter.GetSqlBool(column, Column.Nullable).IsFalse)
  1206. continue;
  1207. // If not first at the list, add comma
  1208. if (list.Length > 0)
  1209. list.Append(Resources.Comma);
  1210. // Append column name
  1211. list.Append(DataInterpreter.GetStringNotNull(column, Column.Name));
  1212. }
  1213. // If list is empty, returns true
  1214. if(list.Length == 0)
  1215. return true;
  1216. // If user chose to return to editor return false
  1217. if( UIHelper.ShowWarning(String.Format(Resources.Warning_NullablePrimaryKey, list.ToString()), MessageBoxButtons.YesNo)
  1218. == DialogResult.No )
  1219. return false;
  1220. // User chose to set all columns to not nullable
  1221. foreach (DataRow column in pk)
  1222. {
  1223. // Check row
  1224. if(column == null)
  1225. {
  1226. Debug.Fail("Null column in array!");
  1227. continue;
  1228. }
  1229. DataInterpreter.SetValueIfChanged( column, Column.Nullable, DataInterpreter.False);
  1230. }
  1231. // Return true, because all columns are marked
  1232. return true;
  1233. }
  1234. /// <summary>
  1235. /// Returns false if there are several autoincrement columns.
  1236. /// </summary>
  1237. /// <returns>Returns false if there are several autoincrement columns.</returns>
  1238. private bool ValidateAutoIncrementColumns()
  1239. {
  1240. // Select all auto increment columns
  1241. DataRow[] columns = DataInterpreter.Select(Columns, Column.IsAutoIncrement, DataInterpreter.True);
  1242. // Empty array is OK
  1243. if (columns == null || columns.Length == 0)
  1244. return true;
  1245. // Only one auto increment column is allowed
  1246. if (columns.Length > 1)
  1247. {
  1248. UIHelper.ShowError(Resources.Error_MultipleAutoIncrement);
  1249. return false;
  1250. }
  1251. // Extract related primary key column
  1252. DataRow primaryKeyColumn = FindIndexColumn(
  1253. IndexDescriptor.PRIMARY,
  1254. DataInterpreter.GetStringNotNull(columns[0], IndexColumn.Name));
  1255. // If this column is not primary key, make it primary key
  1256. if (primaryKeyColumn == null)
  1257. {
  1258. // If user chose to return to editor return false
  1259. if (UIHelper.ShowWarning(
  1260. String.Format(
  1261. Resources.Warning_AutoIncrementIsNotPrimaryKey,
  1262. DataInterpreter.GetStringNotNull(columns[0], Column.Name)),
  1263. MessageBoxButtons.YesNo)
  1264. == DialogResult.No)
  1265. return false;
  1266. // Mark column as primary key and return true
  1267. DataInterpreter.SetValueIfChanged(columns[0], Column.IsPrimaryKey, DataInterpreter.True);
  1268. // Extract new primary key column
  1269. primaryKeyColumn = FindIndexColumn(
  1270. IndexDescriptor.PRIMARY,
  1271. DataInterpreter.GetStringNotNull(columns[0], IndexColumn.Name));
  1272. if(primaryKeyColumn == null)
  1273. {
  1274. Debug.Fail("Failed to get primary key column description");
  1275. // Return true and allow SQL generation. Let SQL parser and executor found this error.
  1276. // This should not happen if columns changes handling works correctly.
  1277. return true;
  1278. }
  1279. // Make new primary key column first
  1280. MakeFirstPrimaryKeyColumn(primaryKeyColumn);
  1281. }
  1282. // Check primary key column ordinal
  1283. if (DataInterpreter.GetInt(primaryKeyColumn, IndexColumn.Ordinal) != 1)
  1284. {
  1285. // Display warning and if user chose to return to editor return false
  1286. if (UIHelper.ShowWarning(
  1287. String.Format(
  1288. Resources.Warning_AutoIncrementIsNotFirstPrimaryKey,
  1289. DataInterpreter.GetStringNotNull(columns[0], Column.Name)),
  1290. MessageBoxButtons.YesNo)
  1291. == DialogResult.No)
  1292. return false;
  1293. // Make primary key column first
  1294. MakeFirstPrimaryKeyColumn(primaryKeyColumn);
  1295. }
  1296. // Finaly return true
  1297. return true;
  1298. }
  1299. /// <summary>
  1300. /// Returns false if any foreign key or foreign key column is not valid.
  1301. /// </summary>
  1302. /// <returns>Returns false if any foreign key or foreign key column is not valid.</returns>
  1303. private bool ValidateForeignKeys()
  1304. {
  1305. // Validate foreign keys
  1306. foreach (DataRow key in ForeignKeys.Rows)
  1307. if (key != null && key.RowState != DataRowState.Deleted && !IsValidForeignKey(key))
  1308. return false;
  1309. return true;
  1310. }
  1311. /// <summary>
  1312. /// Returns false if given foreign key is not valid.
  1313. /// </summary>
  1314. /// <param name="key">DataRow with key data.</param>
  1315. /// <returns>Returns false if given foreign key is not valid.</returns>
  1316. private bool IsValidForeignKey(DataRow key)
  1317. {
  1318. // Name should not be empty
  1319. string name = DataInterpreter.GetString(key, ForeignKey.Name);
  1320. if (String.IsNullOrEmpty(name))
  1321. {
  1322. UIHelper.ShowError(Resources.Error_EmptyForeignKeyName);
  1323. return false;
  1324. }
  1325. // Name should be valid identifier
  1326. if (!Parser.IsValidIdentifier(name))
  1327. {
  1328. UIHelper.ShowError(String.Format(
  1329. Resources.Error_InvalidForeignKeyName,
  1330. name,
  1331. Name));
  1332. return false;
  1333. }
  1334. // Referenced table name should not be empty
  1335. if (!DataInterpreter.IsNotEmptyString(key, ForeignKey.ReferencedTableName))
  1336. {
  1337. UIHelper.ShowError(String.Format(Resources.Error_EmptyReferencedTable, name));
  1338. return false;
  1339. }
  1340. // Check columns (must be at least one)
  1341. DataRow[] columns = DataInterpreter.Select(
  1342. ForeignKeysColumns,
  1343. ForeignKeyColumn.ForeignKeyName,
  1344. name,
  1345. ForeignKeyColumn.OrdinalPosition);
  1346. if (columns == null || columns.Length <= 0)
  1347. {
  1348. UIHelper.ShowError(String.Format(Resources.Error_NoColumnsInForeignKey, name));
  1349. return false;
  1350. }
  1351. // Check if SET NULL action is used
  1352. if (DataInterpreter.CompareInvariant(
  1353. DataInterpreter.GetStringNotNull(key, ForeignKey.OnDelete),
  1354. ForeignKeyDescriptor.SETNULL)
  1355. || DataInterpreter.CompareInvariant(
  1356. DataInterpreter.GetStringNotNull(key, ForeignKey.OnUpdate),
  1357. ForeignKeyDescriptor.SETNULL))
  1358. {
  1359. // Check that each foreign key column is nulable
  1360. StringBuilder list = new StringBuilder();
  1361. foreach (DataRow fkColumn in columns)
  1362. {
  1363. if (fkColumn == null)
  1364. continue;
  1365. // Extract source column
  1366. DataRow column = FindColumn(DataInterpreter.GetStringNotNull(fkColumn, ForeignKeyColumn.Name));
  1367. // Don't show error on it because it will be showed later
  1368. if (column == null)
  1369. continue;
  1370. // Check that column allow nulls, if not, add to list
  1371. if (DataInterpreter.GetSqlBool(column, Column.Nullable).IsFalse)
  1372. {
  1373. if(list.Length > 0)
  1374. list.Append(Resources.Comma);
  1375. list.Append(DataInterpreter.GetStringNotNull(column, Column.Name));
  1376. }
  1377. }
  1378. // If list is not empty, show error
  1379. if (list.Length > 0)
  1380. {
  1381. UIHelper.ShowError(
  1382. String.Format(
  1383. Resources.Error_InvalidSetNull,
  1384. name,
  1385. list.ToString()));
  1386. return false;
  1387. }
  1388. }
  1389. // Finaly, validate foreign key columns
  1390. return ValidateForeignKeyColumns(key, columns);
  1391. }
  1392. /// <summary>
  1393. /// Returns false if any of given foreign keys columns are not valid.
  1394. /// </summary>
  1395. /// <param name="key">DataRow with key data.</param>
  1396. /// <param name="columns">Arrya with foreign keys columns.</param>
  1397. /// <returns>Returns false if any of given foreign keys columns are not valid.</returns>
  1398. private bool ValidateForeignKeyColumns(DataRow key, DataRow[] columns)
  1399. {
  1400. // Extract key and referenced table names
  1401. string keyName = DataInterpreter.GetStringNotNull(key, ForeignKey.Name);
  1402. string referencedTable = DataInterpreter.GetStringNotNull(key, ForeignKey.ReferencedTableName);
  1403. Debug.Assert(!String.IsNullOrEmpty(keyName) && !String.IsNullOrEmpty(referencedTable), "Empty key name or referenced table name!");
  1404. // Enumerate referenced table columns
  1405. DataTable referencedColumns = ColumnDescriptor.Enumerate(Connection, new object[] { null, Schema, referencedTable });
  1406. Debug.Assert(referencedColumns != null, "Failed to read referenced table columns!");
  1407. // Enumerate referenced table index columns
  1408. DataTable indexColumns = IndexColumnDescriptor.Enumerate(Connection, new object[] { null, Schema, referencedTable });
  1409. Debug.Assert(indexColumns != null, "Failed to read referenced table index columns!");
  1410. // Iterate through foreign key columns
  1411. for (int i = 0; i < columns.Length; i++ )
  1412. {
  1413. DataRow column = columns[i];
  1414. if (column == null || column.RowState == DataRowState.Deleted)
  1415. continue;
  1416. // Get source column
  1417. DataRow source = FindColumn(DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.Name));
  1418. if (source == null)
  1419. {
  1420. UIHelper.ShowError(String.Format(
  1421. Resources.Error_NoSourceColumnForForeignKeyColumn,
  1422. DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.Name),
  1423. keyName));
  1424. return false;
  1425. }
  1426. // Get referenced column name
  1427. string referencedName = DataInterpreter.GetString(column, ForeignKeyColumn.ReferencedColumn);
  1428. if (String.IsNullOrEmpty(referencedName))
  1429. {
  1430. UIHelper.ShowError(String.Format(
  1431. Resources.Error_EmptyReferencedColumn,
  1432. DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.Name),
  1433. keyName));
  1434. return false;
  1435. }
  1436. // If have no referenced table columns, continue (do not want to fall on it)
  1437. if (referencedColumns == null)
  1438. continue;
  1439. // Get referenced column information
  1440. DataRow reference = referencedColumns.Rows.Find(new object[] { Schema, referencedTable, referencedName });
  1441. if (reference == null)
  1442. {
  1443. UIHelper.ShowError(String.Format(
  1444. Resources.Error_NoReferencedColumn,
  1445. new object[] {
  1446. DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.Name),
  1447. keyName,
  1448. referencedName,
  1449. referencedTable}));
  1450. return false;
  1451. }
  1452. // Extract information
  1453. string sourceType = DataInterpreter.GetStringNotNull(source, Column.MySqlType);
  1454. string referenceType = DataInterpreter.GetStringNotNull(reference, Column.MySqlType);
  1455. // Add unsigned option
  1456. if (DataInterpreter.GetSqlBool(source, Column.Unsigned))
  1457. sourceType += " unsigned";
  1458. if (DataInterpreter.GetSqlBool(reference, Column.Unsigned))
  1459. referenceType += " unsigned";
  1460. // Compare types
  1461. if (!DataInterpreter.CompareInvariant(sourceType, referenceType))
  1462. {
  1463. UIHelper.ShowError(String.Format(
  1464. Resources.Error_IncompatibleTypes,
  1465. new object[] {
  1466. DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.Name),
  1467. keyName,
  1468. referencedName,
  1469. referencedTable,
  1470. referenceType,
  1471. sourceType}));
  1472. return false;
  1473. }
  1474. // If have no referenced table index columns, continue (do not want to fall on it)
  1475. if (indexColumns == null)
  1476. continue;
  1477. // Check, if referenced table has proper index
  1478. DataRow[] index = DataInterpreter.Select(
  1479. indexColumns,
  1480. IndexColumn.Name,
  1481. referencedName,
  1482. IndexColumn.Ordinal,
  1483. i + 1);
  1484. if (index == null || index.Length <= 0)
  1485. {
  1486. UIHelper.ShowError(String.Format(
  1487. Resources.Error_NoIndexForForeignKey,
  1488. new object[] {
  1489. DataInterpreter.GetStringNotNull(column, ForeignKeyColumn.Name),
  1490. keyName,
  1491. referencedName,
  1492. referencedTable}));
  1493. return false;
  1494. }
  1495. }
  1496. // Finaly, it is ok
  1497. return true;
  1498. }
  1499. /// <summary>
  1500. /// Returns false if any index or index column is not valid.
  1501. /// </summary>
  1502. /// <returns>Returns false if any index or index column is not valid.</returns>
  1503. private bool ValidateIndexes()
  1504. {
  1505. // Validate indexes
  1506. foreach (DataRow index in Indexes.Rows)
  1507. if (index != null && index.RowState != DataRowState.Deleted && !IsValidIndex(index))
  1508. return false;
  1509. return true;
  1510. }
  1511. /// <summary>
  1512. /// Returns false if given index is not valid.
  1513. /// </summary>
  1514. /// <param name="key">DataRow with index data.</param>
  1515. /// <returns>Returns false if given index is not valid.</returns>
  1516. private bool IsValidIndex(DataRow index)
  1517. {
  1518. // Name should not be empty
  1519. string name = DataInterpreter.GetString(index, Index.Name);
  1520. if (String.IsNullOrEmpty(name))
  1521. {
  1522. UIHelper.ShowError(Resources.Error_EmptyIndexName);
  1523. return false;
  1524. }
  1525. // Name should be valid identifier
  1526. if (!Parser.IsValidIdentifier(name))
  1527. {
  1528. UIHelper.ShowError(String.Format(
  1529. Resources.Error_InvalidIndexName,
  1530. name,
  1531. Name));
  1532. return false;
  1533. }
  1534. // Check columns (must be at least one)
  1535. DataRow[] columns = DataInterpreter.Select(IndexColumns, IndexColumn.Index, name);
  1536. if (columns == null || columns.Length <= 0)
  1537. {
  1538. UIHelper.ShowError(String.Format(Resources.Error_NoColumnsInIndex, name));
  1539. return false;
  1540. }
  1541. // Extract index kind
  1542. string kind = DataInterpreter.GetStringNotNull(index, Index.IndexKind);
  1543. // If index is not primary and indexes are not supported, it is a error
  1544. if (!DataInterpreter.CompareInvariant(kind, IndexDescriptor.PRIMARY) && !SupportIndexes)
  1545. {
  1546. UIHelper.ShowError(String.Format(
  1547. Resources.Error_IndexesUnsupported,
  1548. Name,
  1549. Engine));
  1550. return false;
  1551. }
  1552. // Check if index is spatial and not supported
  1553. if (DataInterpreter.CompareInvariant(kind, IndexDescriptor.SPATIAL) && !SupportSpatialIndexes)
  1554. {
  1555. UIHelper.ShowError(String.Format(
  1556. Resources.Error_SpatialIndexesUnsupported,
  1557. name,
  1558. Name,
  1559. Engine));
  1560. return false;
  1561. }
  1562. // Check if index is fulltext and not supported
  1563. if (DataInterpreter.CompareInvariant(kind, IndexDescriptor.FULLTEXT) && !SupportFullTextIndexes)
  1564. {
  1565. UIHelper.ShowError(String.Format(
  1566. Resources.Error_FulltextIndexesUnsupported,
  1567. name,
  1568. Name,
  1569. Engine));
  1570. return false;
  1571. }
  1572. // Finaly, validate index columns
  1573. return ValidateIndexColumns(index, columns);
  1574. }
  1575. /// <summary>
  1576. /// Returns false if any of given index columns are not valid.
  1577. /// </summary>
  1578. /// <param name="index">DataRow with index data.</param>
  1579. /// <param name="columns">Array with index columns.</param>
  1580. /// <returns>Returns false if any of given index columns are not valid.</returns>
  1581. private bool ValidateIndexColumns(DataRow index, DataRow[] columns)
  1582. {
  1583. // Extract key and referenced table names
  1584. string indexName = DataInterpreter.GetStringNotNull(index, Index.Name);
  1585. string indexKind = DataInterpreter.GetStringNotNull(index, Index.IndexKind);
  1586. Debug.Assert(!String.IsNullOrEmpty(indexName) && !String.IsNullOrEmpty(indexKind), "Empty index name or index kind!");
  1587. // Iterate through foreign key columns
  1588. foreach (DataRow column in columns)
  1589. {
  1590. if (column == null || column.RowState == DataRowState.Deleted)
  1591. continue;
  1592. // Get name
  1593. string name = DataInterpreter.GetStringNotNull(column, IndexColumn.Name);
  1594. // Get source column
  1595. DataRow source = FindColumn(name);
  1596. if (source == null)
  1597. {
  1598. UIHelper.ShowError(String.Format(
  1599. Resources.Error_NoSourceColumnForIndexColumn,
  1600. name,
  1601. indexName));
  1602. return false;
  1603. }
  1604. // Extract type information
  1605. string type = DataInterpreter.GetStringNotNull(source, Column.MySqlType);
  1606. bool nullable = DataInterpreter.GetSqlBool(source, Column.Nullable).IsTrue;
  1607. // Check if BLOB/TEXT supported
  1608. if (!SupportBlobAndTextIndexes && (Parser.IsTextType(type) || Parser.IsBlobType(type)))
  1609. {
  1610. UIHelper.ShowError(String.Format(
  1611. Resources.Error_IndexOnBlob,
  1612. name,
  1613. indexName,
  1614. Engine));
  1615. return false;
  1616. }
  1617. // Check if nullable columns supported
  1618. if (!SupportNullIndexes && nullable)
  1619. {
  1620. UIHelper.ShowError(String.Format(
  1621. Resources.Error_IndexOnNull,
  1622. name,
  1623. indexName,
  1624. Engine));
  1625. return false;
  1626. }
  1627. // Aditional checks for spatial indexes
  1628. if (DataInterpreter.CompareInvariant(indexKind, IndexDescriptor.SPATIAL))
  1629. {
  1630. // Column should have spatial type
  1631. if (!Parser.IsSpatialType(type))
  1632. {
  1633. UIHelper.ShowError(String.Format(
  1634. Resources.Error_NonSpatialType,
  1635. name,
  1636. indexName,
  1637. type));
  1638. return false;
  1639. }
  1640. // Column must be not nullable
  1641. if (nullable)
  1642. {
  1643. UIHelper.ShowError(String.Format(
  1644. Resources.Error_NulableColumnInSpatialIndex,
  1645. name,
  1646. indexName));
  1647. return false;
  1648. }
  1649. }
  1650. // Aditional check for fulltext indexes (column should have TEXT or character type)
  1651. if (DataInterpreter.CompareInvariant(indexKind, IndexDescriptor.FULLTEXT)
  1652. && !Parser.IsCharacterType(type))
  1653. {
  1654. UIHelper.ShowError(String.Format(
  1655. Resources.Error_NonCharacterType,
  1656. name,
  1657. indexName,
  1658. type));
  1659. return false;
  1660. }
  1661. }
  1662. // Finaly, it is ok
  1663. return true;
  1664. }
  1665. #endregion
  1666. #region Table Database Object properties
  1667. #region Value enums
  1668. /// <summary>
  1669. /// Enumeration with posible row format option values.
  1670. /// </summary>
  1671. public enum RowFormatValues
  1672. {
  1673. DEFAULT,
  1674. DYNAMIC,
  1675. FIXED,
  1676. COMPRESSED,
  1677. REDUNDANT,
  1678. COMPACT
  1679. }
  1680. /// <summary>
  1681. /// Enumeration with posible pack keys option values.
  1682. /// </summary>
  1683. public enum PackKeysValues
  1684. {
  1685. Default,
  1686. All,
  1687. None
  1688. }
  1689. /// <summary>
  1690. /// Enumeration with posible insert method option values.
  1691. /// </summary>
  1692. public enum InsertMethodValues
  1693. {
  1694. NO,
  1695. FIRST,
  1696. LAST
  1697. }
  1698. #endregion
  1699. /// <summary>
  1700. /// Table engine property. Converts underlying string values to the proper enum
  1701. /// and vise versa.
  1702. /// </summary>
  1703. [LocalizableCategory("Category_Base")]
  1704. [LocalizableDescription("Description_Table_Engine")]
  1705. [LocalizableDisplayName("DisplayName_Table_Engine")]
  1706. [TypeConverter(typeof(EngineConverter))]
  1707. public string Engine
  1708. {
  1709. get
  1710. {
  1711. string result = GetAttributeAsString(Table.Engine);
  1712. // Replacing MRG_MyISAM by more readable MERGE
  1713. if (DataInterpreter.CompareInvariant(result, TableDescriptor.MRG_MyISAM))
  1714. result = TableDescriptor.MERGE;
  1715. return result;
  1716. }
  1717. set
  1718. {
  1719. SetAttribute(Table.Engine, value);
  1720. }
  1721. }
  1722. /// <summary>
  1723. /// Defines how the rows should be stored. For MyISAM tables, the option value
  1724. /// can be FIXED or DYNAMIC for static or variable-length row format. myisampack
  1725. /// sets the type to COMPRESSED.
  1726. /// </summary>
  1727. [LocalizableCategory("Category_Advanced")]
  1728. [LocalizableDescription("Description_Table_RowFormat_Option")]
  1729. [LocalizableDisplayName("DisplayName_Table_RowFormat_Option")]
  1730. [DefaultValue(RowFormatValues.DEFAULT)]
  1731. public RowFormatValues RowFormat
  1732. {
  1733. get
  1734. {
  1735. // Extract initial
  1736. string value = GetAttributeAsString(Table.RowFormatField);
  1737. if (value == null)
  1738. return RowFormatValues.DEFAULT;
  1739. try
  1740. {
  1741. string[] names = Enum.GetNames(typeof(RowFormatValues));
  1742. // Searches for proper value (do not want to cause exception)
  1743. foreach (string name in names)
  1744. if (DataInterpreter.CompareInvariant(name, value))
  1745. return (RowFormatValues)Enum.Parse(typeof(RowFormatValues), value, true);
  1746. return RowFormatValues.DEFAULT;
  1747. }
  1748. catch (ArgumentException)
  1749. {
  1750. return RowFormatValues.DEFAULT;
  1751. }
  1752. }
  1753. set
  1754. {
  1755. SetAttribute(Table.RowFormatField, value);
  1756. }
  1757. }
  1758. /// <summary>
  1759. /// The initial AUTO_INCREMENT value for the table. Only for MyISAM.
  1760. /// </summary>
  1761. [LocalizableCategory("Category_Advanced")]
  1762. [LocalizableDescription("Description_Table_AutoIncrement")]
  1763. [LocalizableDisplayName("DisplayName_Table_AutoIncrement")]
  1764. [DefaultValue(1)]
  1765. public Nullable<Int64> AutoIncrement
  1766. {
  1767. get
  1768. {
  1769. return GetAttributeAsInt(Table.AutoIncrement);
  1770. }
  1771. set
  1772. {
  1773. // Once set, auto increment can not be removed
  1774. if (value == null && GetOldAttributeAsInt(Table.AutoIncrement) != null)
  1775. SetAttribute(Table.AutoIncrement, 1);
  1776. else
  1777. SetAttribute(Table.AutoIncrement, value);
  1778. }
  1779. }
  1780. /// <summary>
  1781. /// An approximation of the average row length for the table. Set this
  1782. /// value only for large tables with variable-size records.
  1783. /// </summary>
  1784. [LocalizableCategory("Category_Advanced")]
  1785. [LocalizableDescription("Description_Table_AverageRowLength_Option")]
  1786. [LocalizableDisplayName("DisplayName_Table_AverageRowLength_Option")]
  1787. [DefaultValue(null)]
  1788. public Nullable<Int64> AverageRowLength
  1789. {
  1790. get
  1791. {
  1792. return GetAttributeAsInt(Table.AverageRowLengthField);
  1793. }
  1794. set
  1795. {
  1796. SetAttribute(Table.AverageRowLengthField, value);
  1797. }
  1798. }
  1799. /// <summary>
  1800. /// The minimum number of rows you plan to store in the table.
  1801. /// </summary>
  1802. [LocalizableCategory("Category_Advanced")]
  1803. [LocalizableDescription("Description_Table_MinRows_Option")]
  1804. [LocalizableDisplayName("DisplayName_Table_MinRows_Option")]
  1805. [DefaultValue(null)]
  1806. public Nullable<Int64> MinRows
  1807. {
  1808. get
  1809. {
  1810. return GetAttributeAsInt(Table.MinRows);
  1811. }
  1812. set
  1813. {
  1814. SetAttribute(Table.MinRows, value);
  1815. }
  1816. }
  1817. /// <summary>
  1818. /// The maximum number of rows you plan to store in the table. This is not a
  1819. /// hard limit, but rather a hint to the storage engine that the table must be
  1820. /// able to store at least this many rows.
  1821. /// </summary>
  1822. [LocalizableCategory("Category_Advanced")]
  1823. [LocalizableDescription("Description_Table_MaxRows_Option")]
  1824. [LocalizableDisplayName("DisplayName_Table_MaxRows_Option")]
  1825. [DefaultValue(null)]
  1826. public Nullable<Int64> MaxRows
  1827. {
  1828. get
  1829. {
  1830. return GetAttributeAsInt(Table.MaxRows);
  1831. }
  1832. set
  1833. {
  1834. SetAttribute(Table.MaxRows, value);
  1835. }
  1836. }
  1837. /// <summary>
  1838. /// Set this option if you want MySQL to maintain a live checksum for all rows
  1839. /// (that is, a checksum that MySQL updates automatically as the table changes).
  1840. /// This makes the table a little slower to update, but also makes it easier to
  1841. /// find corrupted tables. The CHECKSUM TABLE statement reports the checksum.
  1842. /// (MyISAM only.)
  1843. /// </summary>
  1844. [LocalizableCategory("Category_Advanced")]
  1845. [LocalizableDescription("Description_Table_Checksum_Option")]
  1846. [LocalizableDisplayName("DisplayName_Table_Checksum_Option")]
  1847. [DefaultValue(false)]
  1848. public bool Checksum
  1849. {
  1850. get
  1851. {
  1852. // True only if set 1. RTFM 13.1.5
  1853. return GetAttributeAsInt(Table.ChecksumField) == 1;
  1854. }
  1855. set
  1856. {
  1857. // To set on, set to 1. To set off, set to 0. RTFM 13.1.5
  1858. SetAttribute(Table.ChecksumField, value ? 1 : 0);
  1859. }
  1860. }
  1861. /// <summary>
  1862. /// The default character set that is used for the database object.
  1863. /// </summary>
  1864. [LocalizableCategory("Category_Base")]
  1865. [LocalizableDescription("Description_Object_CharacterSet")]
  1866. [LocalizableDisplayName("DisplayName_Object_CharacterSet")]
  1867. [TypeConverter(typeof(CharacterSetConverter))]
  1868. public string CharacterSet
  1869. {
  1870. get
  1871. {
  1872. return GetAttributeAsString(Table.CharacterSet);
  1873. }
  1874. set
  1875. {
  1876. SetAttribute(Table.CharacterSet, value);
  1877. }
  1878. }
  1879. /// <summary>
  1880. /// The collation that is used to compare text for the database object.
  1881. /// </summary>
  1882. [LocalizableCategory("Category_Base")]
  1883. [LocalizableDescription("Description_Object_Collation")]
  1884. [LocalizableDisplayName("DisplayName_Object_Collation")]
  1885. [TypeConverter(typeof(CollationConverter))]
  1886. public string Collation
  1887. {
  1888. get
  1889. {
  1890. return GetAttributeAsString(Table.Collation);
  1891. }
  1892. set
  1893. {
  1894. SetAttribute(Table.Collation, value);
  1895. }
  1896. }
  1897. /// <summary>
  1898. /// The connection string for a FEDERATED table. This option is available as of
  1899. /// MySQL 5.0.13; before that, use a COMMENT option for the connection string.
  1900. /// </summary>
  1901. [LocalizableCategory("Category_Advanced")]
  1902. [LocalizableDescription("Description_Table_Connection_Option")]
  1903. [LocalizableDisplayName("DisplayName_Table_Connection_Option")]
  1904. [DefaultValue(null)]
  1905. public string ConnectionForFederate
  1906. {
  1907. get
  1908. {
  1909. return GetAttributeAsString(Table.Connection);
  1910. }
  1911. set
  1912. {
  1913. SetAttribute(Table.Connection, value);
  1914. }
  1915. }
  1916. /// <summary>
  1917. /// By using this option you can specify where the MyISAM storage engine should put a
  1918. /// table's data file. The directory must be the full pathname to the directory, not
  1919. /// a relative path.
  1920. /// </summary>
  1921. [LocalizableCategory("Category_Advanced")]
  1922. [LocalizableDescription("Description_Table_DataDirectory_Option")]
  1923. [LocalizableDisplayName("DisplayName_Table_DataDirectory_Option")]
  1924. [DefaultValue(null)]
  1925. [Editor(typeof(FolderNameEditor),typeof(UITypeEditor))]
  1926. public string DataDirectory
  1927. {
  1928. get
  1929. {
  1930. return GetAttributeAsString(Table.DataDirectory);
  1931. }
  1932. set
  1933. {
  1934. SetAttribute(Table.DataDirectory, value);
  1935. }
  1936. }
  1937. /// <summary>
  1938. /// By using this option you can specify where the MyISAM storage engine should put a
  1939. /// table's index file. The directory must be the full pathname to the directory, not
  1940. /// a relative path.
  1941. /// </summary>
  1942. [LocalizableCategory("Category_Advanced")]
  1943. [LocalizableDescription("Description_Table_IndexDirectory_Option")]
  1944. [LocalizableDisplayName("DisplayName_Table_IndexDirectory_Option")]
  1945. [DefaultValue(null)]
  1946. [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
  1947. public string IndexDirectory
  1948. {
  1949. get
  1950. {
  1951. return GetAttributeAsString(Table.IndexDirectory);
  1952. }
  1953. set
  1954. {
  1955. SetAttribute(Table.IndexDirectory, value);
  1956. }
  1957. }
  1958. /// <summary>
  1959. /// Set this option if you want MySQL to maintain a live checksum for all rows
  1960. /// (that is, a checksum that MySQL updates automatically as the table changes).
  1961. /// This makes the table a little slower to update, but also makes it easier to
  1962. /// find corrupted tables. The CHECKSUM TABLE statement reports the checksum.
  1963. /// (MyISAM only.)
  1964. /// </summary>
  1965. [LocalizableCategory("Category_Advanced")]
  1966. [LocalizableDescription("Description_Table_DelayKeyWrite")]
  1967. [LocalizableDisplayName("DisplayName_Table_DelayKeyWrite")]
  1968. [DefaultValue(false)]
  1969. public bool DeleteKeyWrite
  1970. {
  1971. get
  1972. {
  1973. // True only if set 1. RTFM 13.1.5
  1974. return GetAttributeAsInt(Table.DelayKeyWrite) == 1;
  1975. }
  1976. set
  1977. {
  1978. // To set on, set to 1. To set off, set to 0. RTFM 13.1.5
  1979. SetAttribute(Table.DelayKeyWrite, value ? 1 : 0);
  1980. }
  1981. }
  1982. /// <summary>
  1983. /// Use this option to generate smaller indices. This usually makes updates
  1984. /// slower and reads faster. Setting it to DEFAULT tells the storage engine
  1985. /// to only pack long CHAR/VARCHAR columns.
  1986. /// </summary>
  1987. [LocalizableCategory("Category_Advanced")]
  1988. [LocalizableDescription("Description_Table_PackKeys")]
  1989. [LocalizableDisplayName("DisplayName_Table_PackKeys")]
  1990. [DefaultValue(TableDocument.PackKeysValues.Default)]
  1991. public PackKeysValues PackKeys
  1992. {
  1993. get
  1994. {
  1995. string stringVal = GetAttributeAsString(Table.PackKeys);
  1996. // If nothing, then default
  1997. if (String.IsNullOrEmpty(stringVal))
  1998. return PackKeysValues.Default;
  1999. // If "1", then all
  2000. if (DataInterpreter.CompareInvariant(stringVal, "1"))
  2001. return PackKeysValues.All;
  2002. // If "0", then none
  2003. if (DataInterpreter.CompareInvariant(stringVal, "0"))
  2004. return PackKeysValues.None;
  2005. // In all other cases DEFAULT
  2006. return PackKeysValues.Default;
  2007. }
  2008. set
  2009. {
  2010. switch (value)
  2011. {
  2012. case PackKeysValues.All: // If All, then "1"
  2013. SetAttribute(Table.PackKeys, "1");
  2014. break;
  2015. case PackKeysValues.None: // If None, thne "0"
  2016. SetAttribute(Table.PackKeys, "0");
  2017. break;
  2018. default: // In all other cases "DEFAULT"
  2019. SetAttribute(Table.PackKeys, "DEFAULT");
  2020. break;
  2021. }
  2022. }
  2023. }
  2024. /// <summary>
  2025. /// Use this option to encrypt the .frm file with a password. This option
  2026. /// does nothing in the standard MySQL version.
  2027. /// </summary>
  2028. [LocalizableCategory("Category_Advanced")]
  2029. [LocalizableDescription("Description_Table_Password")]
  2030. [LocalizableDisplayName("DisplayName_Table_Password")]
  2031. public string Password
  2032. {
  2033. get
  2034. {
  2035. return GetAttributeAsString(Table.Password);
  2036. }
  2037. set
  2038. {
  2039. SetAttribute(Table.Password, value);
  2040. }
  2041. }
  2042. /// <summary>
  2043. /// If you want to insert data into a MERGE table, you must specify with
  2044. /// INSERT_METHOD the table into which the row should be inserted.
  2045. /// INSERT_METHOD is an option useful for MERGE tables only. Use a value
  2046. /// of FIRST or LAST to have inserts go to the first or last table, or a
  2047. /// value of NO to prevent inserts.
  2048. /// </summary>
  2049. [LocalizableCategory("Category_Advanced")]
  2050. [LocalizableDescription("Description_Table_InsertMethod")]
  2051. [LocalizableDisplayName("DisplayName_Table_InsertMethod")]
  2052. [DefaultValue(TableDocument.InsertMethodValues.NO)]
  2053. public InsertMethodValues InsertMethod
  2054. {
  2055. get
  2056. {
  2057. string stringVal = GetAttributeAsString(Table.InsertMethod);
  2058. // If nothing, then NO
  2059. if (String.IsNullOrEmpty(stringVal))
  2060. return InsertMethodValues.NO;
  2061. // If "FIRST", then FIRST
  2062. if (DataInterpreter.CompareInvariant(stringVal, InsertMethodValues.FIRST.ToString()))
  2063. return InsertMethodValues.FIRST;
  2064. // If "LAST", then LAST
  2065. if (DataInterpreter.CompareInvariant(stringVal, InsertMethodValues.LAST.ToString()))
  2066. return InsertMethodValues.LAST;
  2067. // In all other cases NO
  2068. return InsertMethodValues.NO;
  2069. }
  2070. set
  2071. {
  2072. SetAttribute(Table.InsertMethod, value);
  2073. }
  2074. }
  2075. /// <summary>
  2076. /// List of MyISAM tables that should be used by the MERGE table.
  2077. /// </summary>
  2078. [LocalizableCategory("Category_Advanced")]
  2079. [LocalizableDescription("Description_Table_Union")]
  2080. [LocalizableDisplayName("DisplayName_Table_Union")]
  2081. public string Union
  2082. {
  2083. get
  2084. {
  2085. return GetAttributeAsString(Table.Union);
  2086. }
  2087. set
  2088. {
  2089. SetAttribute(Table.Union, value);
  2090. }
  2091. }
  2092. #endregion
  2093. #region Public properties
  2094. /// <summary>
  2095. /// DataTable object with lists of all columns for this table. This property
  2096. /// is read-only, but all changes madden to DataTable affects table object too.
  2097. /// This table are read in LoadData.
  2098. /// </summary>
  2099. [Browsable(false)]
  2100. public DataTable Columns
  2101. {
  2102. get
  2103. {
  2104. Debug.Assert(columnsTable != null, "Columns are not read!");
  2105. return columnsTable;
  2106. }
  2107. }
  2108. /// <summary>
  2109. /// DataTable object with lists of all foreign keys for this table. This property
  2110. /// is read-only, but all changes madden to DataTable affects table object too.
  2111. /// This table are read in LoadData.
  2112. /// </summary>
  2113. [Browsable(false)]
  2114. public DataTable ForeignKeys
  2115. {
  2116. get
  2117. {
  2118. Debug.Assert(foreignKeysTable != null, "Foreign keys are not read!");
  2119. return foreignKeysTable;
  2120. }
  2121. }
  2122. /// <summary>
  2123. /// DataTable object with lists of all foreign keys columns for this table. This
  2124. /// property is read-only, but all changes madden to DataTable affects table object
  2125. /// too. This table are read in LoadData.
  2126. /// </summary>
  2127. [Browsable(false)]
  2128. public DataTable ForeignKeysColumns
  2129. {
  2130. get
  2131. {
  2132. Debug.Assert(foreignKeysColumnsTable != null, "Foreign keys columns are not read!");
  2133. return foreignKeysColumnsTable;
  2134. }
  2135. }
  2136. /// <summary>
  2137. /// DataTable object with lists of all indexes for this table. This property is read-only,
  2138. /// but all changes madden to DataTable affects table object too. This table are read in
  2139. /// LoadData.
  2140. /// </summary>
  2141. [Browsable(false)]
  2142. public DataTable Indexes
  2143. {
  2144. get
  2145. {
  2146. Debug.Assert(indexesTable != null, "Indexes are not read!");
  2147. return indexesTable;
  2148. }
  2149. }
  2150. /// <summary>
  2151. /// DataTable object with lists of all index columns for this table. This property is
  2152. /// read-only, but all changes madden to DataTable affects table object too. This table
  2153. /// are read in LoadData.
  2154. /// </summary>
  2155. [Browsable(false)]
  2156. public DataTable IndexColumns
  2157. {
  2158. get
  2159. {
  2160. Debug.Assert(indexColumnsTable != null, "Index columns are not read!");
  2161. return indexColumnsTable;
  2162. }
  2163. }
  2164. #endregion
  2165. #region Indexes information
  2166. /// <summary>
  2167. /// Returns true if indexes are supported. ARCHIVE engine does not support indexes.
  2168. /// </summary>
  2169. [Browsable(false)]
  2170. public bool SupportIndexes
  2171. {
  2172. get
  2173. {
  2174. return !DataInterpreter.CompareInvariant(Engine, TableDescriptor.ARCHIVE);
  2175. }
  2176. }
  2177. /// <summary>
  2178. /// Returns true if FULLTEXT indexes are supported. Only InnoDB engine supports FULLTEXT
  2179. /// indexes.
  2180. /// </summary>
  2181. [Browsable(false)]
  2182. public bool SupportFullTextIndexes
  2183. {
  2184. get
  2185. {
  2186. return DataInterpreter.CompareInvariant(Engine, TableDescriptor.MyISAM);
  2187. }
  2188. }
  2189. /// <summary>
  2190. /// Returns true if SPATIAL indexes are supported. Only MyISAM, InnoDB, NDB and BDB
  2191. /// engines supports SPATIAL indexes (RTFM 16).
  2192. /// </summary>
  2193. [Browsable(false)]
  2194. public bool SupportSpatialIndexes
  2195. {
  2196. get
  2197. {
  2198. return DataInterpreter.CompareInvariant(Engine, TableDescriptor.MyISAM)
  2199. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.InnoDB)
  2200. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.BDB)
  2201. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.NDB);
  2202. }
  2203. }
  2204. /// <summary>
  2205. /// Returns true if hash indexes are supported. Only MEMORY engine supports HASH
  2206. /// indexes.
  2207. /// </summary>
  2208. [Browsable(false)]
  2209. public bool SupportHashIndexes
  2210. {
  2211. get
  2212. {
  2213. return DataInterpreter.CompareInvariant(Engine, TableDescriptor.MEMORY);
  2214. }
  2215. }
  2216. /// <summary>
  2217. /// Returns true if BLOB and TEXT indexes are supported. Only MyISAM, InnoDB and BDB
  2218. /// engines supports BLOB and TEXT indexes (RTFM 13.1.14).
  2219. /// </summary>
  2220. [Browsable(false)]
  2221. public bool SupportBlobAndTextIndexes
  2222. {
  2223. get
  2224. {
  2225. return DataInterpreter.CompareInvariant(Engine, TableDescriptor.MyISAM)
  2226. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.InnoDB)
  2227. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.BDB);
  2228. }
  2229. }
  2230. /// <summary>
  2231. /// Returns true if indexes on nullable columns are supported. Only MyISAM, InnoDB, BDB
  2232. /// and MEMORY engines supports indexes on nullable columns (RTFM 13.1.14).
  2233. /// </summary>
  2234. [Browsable(false)]
  2235. public bool SupportNullIndexes
  2236. {
  2237. get
  2238. {
  2239. return DataInterpreter.CompareInvariant(Engine, TableDescriptor.MyISAM)
  2240. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.InnoDB)
  2241. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.BDB)
  2242. || DataInterpreter.CompareInvariant(Engine, TableDescriptor.MEMORY);
  2243. }
  2244. }
  2245. /// <summary>
  2246. /// Returns array with names of supported index kinds.
  2247. /// </summary>
  2248. /// <returns>Returns array with names of supported index kinds.</returns>
  2249. public string[] GetSupportedIndexKinds()
  2250. {
  2251. if (Attributes == null)
  2252. return null;
  2253. // Collect result into temporary list
  2254. List<string> list = new List<string>();
  2255. // If indexes are not supported, return empty list
  2256. if (!SupportIndexes)
  2257. return new string[0];
  2258. // Simple index and unique are supported by all engines that supports indexes
  2259. list.Add(IndexDescriptor.INDEX);
  2260. list.Add(IndexDescriptor.UNIQUE);
  2261. // Full text indexes supported only by MyISAM
  2262. if (SupportFullTextIndexes)
  2263. list.Add(IndexDescriptor.FULLTEXT);
  2264. // Spatial indexes supported only by MyISAM
  2265. if (SupportSpatialIndexes)
  2266. list.Add(IndexDescriptor.SPATIAL);
  2267. // Prepare and return results
  2268. string[] result = new string[list.Count];
  2269. list.CopyTo(result);
  2270. return result;
  2271. }
  2272. /// <summary>
  2273. /// Returns array with names of supported index types.
  2274. /// </summary>
  2275. /// <returns>Returns array with names of supported index types.</returns>
  2276. public string[] GetSupportedIndexTypes()
  2277. {
  2278. if (Attributes == null)
  2279. return null;
  2280. // Collect result into temporary list
  2281. List<string> list = new List<string>();
  2282. // Simple BTREE are supported by all engines
  2283. list.Add(IndexDescriptor.BTREE);
  2284. // HASH indexes supported only by MEMORY
  2285. if (SupportHashIndexes)
  2286. list.Add(IndexDescriptor.HASH);
  2287. // Prepare and return results
  2288. string[] result = new string[list.Count];
  2289. list.CopyTo(result);
  2290. return result;
  2291. }
  2292. #endregion
  2293. #region Table create or alter SQL generation
  2294. /// <summary>
  2295. /// Builds header for CREATE TABLE request.
  2296. /// </summary>
  2297. /// <param name="target">String builder, used to build query.</param>
  2298. private void BuildCreateHeader(StringBuilder target)
  2299. {
  2300. target.Append("CREATE TABLE ");
  2301. QueryBuilder.WriteIdentifier(Name, target);
  2302. }
  2303. /// <summary>
  2304. /// Builds header for ALTER TABLE request.
  2305. /// </summary>
  2306. /// <param name="target">String builder, used to build query.</param>
  2307. private void BuildAlterHeader(StringBuilder target)
  2308. {
  2309. target.Append("ALTER TABLE ");
  2310. QueryBuilder.WriteIdentifier(OldName, target);
  2311. }
  2312. /// <summary>
  2313. /// Builds create definition, as described in MySQL manual 13.1.5.
  2314. /// </summary>
  2315. /// <param name="target">String builder, used to build query.</param>
  2316. private void BuildCreateDefinition(StringBuilder target)
  2317. {
  2318. BuildColumnsCreation(target);
  2319. // Write primary key columns
  2320. BuildPrimaryKey(target, null);
  2321. // Write indexes
  2322. BuildIndexes(target, null);
  2323. // Write foreign keys
  2324. BuildForeignKeys(target, null);
  2325. // Remove trailing ","
  2326. if (target[target.Length - 1] == ',')
  2327. target[target.Length - 1] = ' ';
  2328. }
  2329. /// <summary>
  2330. /// Builds alter specifications, as described in MySQL manual 13.1.2.
  2331. /// </summary>
  2332. /// <param name="target">String builder, used to build query.</param>
  2333. private void BuildAlterSpecifications(StringBuilder target)
  2334. {
  2335. // If table was renamed
  2336. QueryBuilder.WriteIdentifierIfChanged(Attributes, Table.Name, "\nRENAME TO ", ",", target);
  2337. // Check if primary key need to be changed
  2338. bool needDropKey, needNewKey;
  2339. DetermineRequiredPrimaryKeyChanges(out needDropKey, out needNewKey);
  2340. // Drop primary key if needed
  2341. if (needDropKey)
  2342. target.Append("\nDROP PRIMARY KEY,");
  2343. // Write detected columns changes
  2344. BuildColumnsChanges(target);
  2345. // Create primary key if needed
  2346. if (needNewKey)
  2347. BuildPrimaryKey(target, "ADD ");
  2348. // Write indexes
  2349. BuildIndexes(target, "ADD ");
  2350. // Write foreign keys
  2351. BuildForeignKeys(target, "ADD ");
  2352. // Write options
  2353. BuildTableOptions(target, ",");
  2354. // Remove trailing ","
  2355. if (target[target.Length - 1] == ',')
  2356. target[target.Length - 1] = ';';
  2357. }
  2358. /// <summary>
  2359. /// Builds table options, as described in MySQL manual 13.1.5.
  2360. /// </summary>
  2361. /// <param name="target">String builder, used to build query.</param>
  2362. private void BuildTableOptions(StringBuilder target, string suffix)
  2363. {
  2364. Debug.Assert(Attributes != null, "Attributes are not loaded!");
  2365. // Write table comments
  2366. QueryBuilder.WriteIfChanged(Attributes, Table.Comments, "\nCOMMENT = ", target, suffix);
  2367. // Write table engine
  2368. QueryBuilder.WriteIfChanged(Attributes, Table.Engine, "\nENGINE = ", target, suffix, false);
  2369. // Write character set option
  2370. QueryBuilder.WriteIfChanged(Attributes, Table.CharacterSet, "\nCHARACTER SET ", target, suffix, false);
  2371. // Write character set option
  2372. QueryBuilder.WriteIfChanged(Attributes, Table.Collation, "\nCOLLATE ", target, suffix, false);
  2373. // Write initial autoincrement value
  2374. QueryBuilder.WriteIfChanged(Attributes, Table.AutoIncrement, "\nAUTO_INCREMENT = ", target, suffix, false);
  2375. // Write average row length value
  2376. QueryBuilder.WriteIfChanged(Attributes, Table.AverageRowLengthField, "\nAVG_ROW_LENGTH = ", target, suffix, false);
  2377. // Write min_rows value
  2378. QueryBuilder.WriteIfChanged(Attributes, Table.MinRows, "\nMIN_ROWS = ", target, suffix, false);
  2379. // Write max_rows value
  2380. QueryBuilder.WriteIfChanged(Attributes, Table.MaxRows, "\nMAX_ROWS = ", target, suffix, false);
  2381. // Write row_format value
  2382. QueryBuilder.WriteIfChanged(Attributes, Table.RowFormatField, "\nROW_FORMAT = ", target, suffix, false);
  2383. // Write checksum option
  2384. QueryBuilder.WriteIfChanged(Attributes, Table.ChecksumField, "\nCHECKSUM = ", target, suffix, false);
  2385. // Write connection option
  2386. QueryBuilder.WriteIfChanged(Attributes, Table.Connection, "\nCONNECTION = ", target, suffix);
  2387. // Write data directory option
  2388. QueryBuilder.WriteIfChanged(Attributes, Table.DataDirectory, "\nDATA DIRECTORY = ", target, suffix);
  2389. // Write index directory option
  2390. QueryBuilder.WriteIfChanged(Attributes, Table.IndexDirectory, "\nINDEX DIRECTORY = ", target, suffix);
  2391. // Write delay key write option
  2392. QueryBuilder.WriteIfChanged(Attributes, Table.DelayKeyWrite, "\nDELAY_KEY_WRITE = ", target, suffix, false);
  2393. // Write pack keys option
  2394. QueryBuilder.WriteIfChanged(Attributes, Table.PackKeys, "\nPACK_KEYS = ", target, suffix, false);
  2395. // Write password option
  2396. QueryBuilder.WriteIfChanged(Attributes, Table.Password, "\nPASSWORD = ", target, suffix);
  2397. // Write insert method option
  2398. QueryBuilder.WriteIfChanged(Attributes, Table.InsertMethod, "\nINSERT_METHOD = ", target, suffix, false);
  2399. // Write union option
  2400. QueryBuilder.WriteIfChanged(Attributes, Table.Union, "\nUNION = (", target, ")" + suffix, false);
  2401. }
  2402. #endregion
  2403. #region Columns operations SQL
  2404. /// <summary>
  2405. /// Writes definitionas of table columns.
  2406. /// </summary>
  2407. /// <param name="target">String builder, used to build query.</param>
  2408. private void BuildColumnsCreation(StringBuilder target)
  2409. {
  2410. // Write columns definitions
  2411. foreach (DataRow column in Columns.Select(String.Empty, Column.Ordinal))
  2412. {
  2413. // Start new line
  2414. target.AppendLine();
  2415. BuildColumnDefinition(column, target, null);
  2416. }
  2417. }
  2418. /// <summary>
  2419. /// Writes detected column changes
  2420. /// </summary>
  2421. /// <param name="target">String builder, used to build query.</param>
  2422. private void BuildColumnsChanges(StringBuilder target)
  2423. {
  2424. // Initial after is first
  2425. string after = " FIRST";
  2426. // Iterate through columns
  2427. foreach (DataRow column in Columns.Select(String.Empty, Column.Ordinal, DataViewRowState.Deleted | DataViewRowState.CurrentRows))
  2428. {
  2429. switch (column.RowState)
  2430. {
  2431. // Create definition for new column
  2432. case DataRowState.Added:
  2433. BuildAddColumn(target, column, after);
  2434. break;
  2435. // Create drop column command
  2436. case DataRowState.Deleted:
  2437. BuildDropColumn(target, column);
  2438. break;
  2439. // Create alter column comand
  2440. case DataRowState.Modified:
  2441. // Not all changes should be handled here, for example,
  2442. // primary key status handled by BuildAlterPrimaryKey
  2443. if (IsColumnChanged(column))
  2444. BuildChangeColumn(target, column, after);
  2445. break;
  2446. default:
  2447. break;
  2448. }
  2449. // If row is deleted, continue
  2450. if (column.RowState == DataRowState.Deleted)
  2451. continue;
  2452. // Recaculate after
  2453. after = " AFTER " + QueryBuilder.EscapeAndQuoteIdentifier(
  2454. DataInterpreter.GetStringNotNull(column, Column.Name));
  2455. }
  2456. }
  2457. /// <summary>
  2458. /// Builds column definition, as described in MySQL manual 13.1.15.
  2459. /// </summary>
  2460. /// <param name="column">Data row with column attributes.</param>
  2461. /// <param name="target">String builder, used to build query.</param>
  2462. private void BuildColumnDefinition(DataRow column, StringBuilder target, string after)
  2463. {
  2464. // Write column name
  2465. QueryBuilder.WriteIdentifier(column, Column.Name, target);
  2466. // Write column type
  2467. target.Append(' ');
  2468. BuildDataType(column, target);
  2469. // Write NOT NULL if necessary
  2470. QueryBuilder.WriteIfFalse(column, Column.Nullable, " NOT NULL", target);
  2471. // Write default value if specified (only for not autoincrement columns)
  2472. if (!DataInterpreter.GetSqlBool(column, Column.IsAutoIncrement).IsTrue)
  2473. {
  2474. if (Parser.IsNumericType(DataInterpreter.GetStringNotNull(column, Column.MySqlType)))
  2475. // For integers we neve use quotes
  2476. QueryBuilder.WriteIfNotEmptyString(column, Column.Default, " DEFAULT ", target, false);
  2477. else if (Parser.IsDateTimeType(DataInterpreter.GetStringNotNull(column, Column.MySqlType)))
  2478. // For datetimes we should use quotes only if punctuation character is used
  2479. QueryBuilder.WriteIfNotEmptyString(column, Column.Default, " DEFAULT ", target,
  2480. Parser.HasPunctuation(DataInterpreter.GetStringNotNull(column, Column.Default)));
  2481. else
  2482. // Other types are always quoted and empty string is allowed
  2483. QueryBuilder.WriteIfNotNull(column, Column.Default, " DEFAULT ", target);
  2484. }
  2485. // Write auto increment flag
  2486. QueryBuilder.WriteIfTrue(column, Column.IsAutoIncrement, " AUTO_INCREMENT", target);
  2487. // Write column comments
  2488. QueryBuilder.WriteIfNotEmptyString(column, Column.Comments, " COMMENT ", target);
  2489. // Write after definition, if any
  2490. QueryBuilder.WriteIfNotEmptyString(after, null, target, false);
  2491. // Write trailing ","
  2492. target.Append(",");
  2493. }
  2494. /// <summary>
  2495. /// Builds CHANGE COLUMN or MODIFY COLUMN definition, as described in
  2496. /// MySQL manual 13.1.2.
  2497. /// </summary>
  2498. /// <param name="column">Data row with column attributes.</param>
  2499. /// <param name="target">String builder, used to build query.</param>
  2500. private void BuildChangeColumn(StringBuilder target, DataRow column, string after)
  2501. {
  2502. if (DataInterpreter.HasChanged(column, Column.Name))
  2503. {
  2504. target.Append("\nCHANGE COLUMN ");
  2505. QueryBuilder.WriteOldIdentifier(column, Column.Name, target);
  2506. target.Append(" ");
  2507. }
  2508. else
  2509. {
  2510. target.Append("\nMODIFY COLUMN ");
  2511. }
  2512. BuildColumnDefinition(column, target, after);
  2513. }
  2514. /// <summary>
  2515. /// Builds DROP COLUMN definition, as described in MySQL manual 13.1.2.
  2516. /// </summary>
  2517. /// <param name="column">Data row with column attributes.</param>
  2518. /// <param name="target">String builder, used to build query.</param>
  2519. private void BuildDropColumn(StringBuilder target, DataRow column)
  2520. {
  2521. target.Append("\nDROP COLUMN ");
  2522. QueryBuilder.WriteOldIdentifier(column, Column.Name, target);
  2523. // Write trailing ","
  2524. target.Append(",");
  2525. }
  2526. /// <summary>
  2527. /// Builds ADD COLUMN definition, as described in MySQL manual 13.1.2.
  2528. /// </summary>
  2529. /// <param name="column">Data row with column attributes.</param>
  2530. /// <param name="target">String builder, used to build query.</param>
  2531. private void BuildAddColumn(StringBuilder target, DataRow column, string after)
  2532. {
  2533. target.Append("\nADD COLUMN ");
  2534. BuildColumnDefinition(column, target, after);
  2535. }
  2536. /// <summary>
  2537. /// Builds data type definition, as described in MySQL manual 13.1.15.
  2538. /// </summary>
  2539. /// <param name="column">Data row with column attributes.</param>
  2540. /// <param name="target">String builder, used to build query.</param>
  2541. private void BuildDataType(DataRow column, StringBuilder target)
  2542. {
  2543. string dataType = DataInterpreter.GetStringNotNull(column, Column.MySqlType);
  2544. target.Append(dataType);
  2545. // If numeric, check for UNSIGNED and ZEROFILL
  2546. if (Parser.IsNumericType(dataType))
  2547. {
  2548. QueryBuilder.WriteIfTrue(column, Column.Unsigned, " UNSIGNED", target);
  2549. QueryBuilder.WriteIfTrue(column, Column.Zerofill, " ZEROFILL", target);
  2550. }
  2551. // If supports binary, check for binary
  2552. if( Parser.SupportBinary(dataType) )
  2553. QueryBuilder.WriteIfTrue(column, Column.Binary, " BINARY", target);
  2554. // If supports ASCII and UNICODE, check for them
  2555. if (Parser.SupportAsciiAndUnicode(dataType))
  2556. {
  2557. QueryBuilder.WriteIfTrue(column, Column.Ascii, " ASCII", target);
  2558. QueryBuilder.WriteIfTrue(column, Column.Unicode, " UNICODE", target);
  2559. }
  2560. // If supports character set and colltion, check for them
  2561. if (Parser.SupportCharacterSet(dataType))
  2562. {
  2563. // Write character set if differs from default for table or changed
  2564. if (!DataInterpreter.CompareInvariant(
  2565. CharacterSet,
  2566. DataInterpreter.GetStringNotNull(column, Column.CharacterSet))
  2567. || DataInterpreter.HasChanged(column, Column.CharacterSet))
  2568. {
  2569. QueryBuilder.WriteIfNotEmptyString(column, Column.CharacterSet, " CHARACTER SET ", target, false);
  2570. }
  2571. // Write collation if differs from default for table or changed
  2572. if (!DataInterpreter.CompareInvariant(
  2573. Collation,
  2574. DataInterpreter.GetStringNotNull(column, Column.Collation))
  2575. || DataInterpreter.HasChanged(column, Column.Collation))
  2576. {
  2577. QueryBuilder.WriteIfNotEmptyString(column, Column.Collation, " COLLATE ", target, false);
  2578. }
  2579. }
  2580. }
  2581. /// <summary>
  2582. /// Checks, if column changes should be processed by BuildChangeColumn method.
  2583. /// Not any change cause MODIFY and CHANGE COLUMN statemets to be inserted. For
  2584. /// example, if only primary key status of the column was changed, it will be
  2585. /// processed by BuildAlterPrimaryKey method.
  2586. /// </summary>
  2587. /// <param name="column">DataRow with column attributes to check.</param>
  2588. /// <returns>
  2589. /// Returns true if changes should be handled by method and false otherwise.
  2590. /// </returns>
  2591. private bool IsColumnChanged(DataRow column)
  2592. {
  2593. // Look for changed columns attributes
  2594. foreach (DataColumn tableColumn in column.Table.Columns)
  2595. {
  2596. if (DataInterpreter.HasChanged(column, tableColumn.ColumnName))
  2597. {
  2598. // If primary key status was changed, it will be handeled
  2599. // by BuildAlterPrimaryKey
  2600. if (DataInterpreter.CompareInvariant(
  2601. tableColumn.ColumnName,
  2602. Column.IsPrimaryKey))
  2603. continue;
  2604. // All other changes need to be handeled by BuildChangeColumn
  2605. return true;
  2606. }
  2607. }
  2608. // No changes found for BuildChangeColumn
  2609. return false;
  2610. }
  2611. #endregion
  2612. #region Foreign key SQL generation
  2613. /// <summary>
  2614. /// Builds pre-query to drop foreign keys. This is necessary because of
  2615. /// bug http://bugs.mysql.com/bug.php?id=8377 .
  2616. /// </summary>
  2617. /// <returns>Returns pre-query to drop foreign keys</returns>
  2618. private string BuildPreDropForeignKeys()
  2619. {
  2620. StringBuilder target = new StringBuilder();
  2621. // Write header
  2622. BuildAlterHeader(target);
  2623. // Iterate through foreign keys and drop them
  2624. foreach (DataRow key in ForeignKeys.Rows)
  2625. {
  2626. switch (key.RowState)
  2627. {
  2628. // Skip added and deleted keys
  2629. case DataRowState.Added:
  2630. case DataRowState.Deleted:
  2631. break;
  2632. // Check if this key was changed and drop it
  2633. default:
  2634. if (HasForeignKeyChanged(key))
  2635. BuildDropForeignKey(target, key);
  2636. break;
  2637. }
  2638. }
  2639. // Remove trailing ","
  2640. if (target[target.Length - 1] == ',')
  2641. target[target.Length - 1] = ' ';
  2642. // Append new line
  2643. return target.ToString();
  2644. }
  2645. /// <summary>
  2646. /// Returns true if any of the tables foreign keys was changed and need to be
  2647. /// pre-droped. This is necessary because of bug http://bugs.mysql.com/bug.php?id=8377 .
  2648. /// </summary>
  2649. /// <returns>
  2650. /// Returns true if any of the tables foreign keys was changed and need to be
  2651. /// pre-droped.
  2652. /// </returns>
  2653. private bool NeedToDropForeignKeys()
  2654. {
  2655. // Iterate through foreign keys and check them
  2656. foreach (DataRow key in ForeignKeys.Rows)
  2657. {
  2658. switch (key.RowState)
  2659. {
  2660. // Skip added and deleted keys
  2661. case DataRowState.Added:
  2662. case DataRowState.Deleted:
  2663. break;
  2664. // Check if this key was changed
  2665. default:
  2666. if (HasForeignKeyChanged(key))
  2667. return true;
  2668. break;
  2669. }
  2670. }
  2671. return false;
  2672. }
  2673. /// <summary>
  2674. /// Build definitions for changes in the foreign keys.
  2675. /// </summary>
  2676. /// <param name="target">String builder, used to build query.</param>
  2677. /// <param name="prefix">
  2678. /// Prefix to add before definition. Could be empty or "ADD "
  2679. /// </param>
  2680. private void BuildForeignKeys(StringBuilder target, string prefix)
  2681. {
  2682. // Iterate through foreign keys
  2683. foreach (DataRow key in ForeignKeys.Rows)
  2684. {
  2685. switch (key.RowState)
  2686. {
  2687. // Create definition for new foreign key
  2688. case DataRowState.Added:
  2689. BuildAddForeignKey(target, key, prefix);
  2690. break;
  2691. // Create drop key command
  2692. case DataRowState.Deleted:
  2693. BuildDropForeignKey(target, key);
  2694. break;
  2695. // Create DROP/ADD key, if needed
  2696. default:
  2697. if (HasForeignKeyChanged(key))
  2698. BuildAddForeignKey(target, key, prefix);
  2699. break;
  2700. }
  2701. }
  2702. }
  2703. /// <summary>
  2704. /// Builds DROP FOREIGN KEY statement
  2705. /// </summary>
  2706. /// <param name="target">String builder, used to build query.</param>
  2707. /// <param name="key">DataRow with foreign key data.</param>
  2708. private void BuildDropForeignKey(StringBuilder target, DataRow key)
  2709. {
  2710. target.Append("\nDROP FOREIGN KEY ");
  2711. QueryBuilder.WriteOldIdentifier(key, ForeignKey.Name, target);
  2712. // Write trailing ","
  2713. target.Append(",");
  2714. // If we have index with same name, drop it too
  2715. if (key.HasVersion(DataRowVersion.Original))
  2716. {
  2717. DataRow index = FindIndex(DataInterpreter.GetStringNotNull(key, ForeignKey.Name, DataRowVersion.Original));
  2718. if (index != null)
  2719. BuildDropIndex(target, index);
  2720. }
  2721. }
  2722. /// <summary>
  2723. /// Builds ADD FOREIGN KEY statement
  2724. /// </summary>
  2725. /// <param name="target">String builder, used to build query.</param>
  2726. /// <param name="key">DataRow with foreign key data.</param>
  2727. /// <param name="prefix">
  2728. /// Prefix to add before definition. Could be empty or "ADD "
  2729. /// </param>
  2730. private void BuildAddForeignKey(StringBuilder target, DataRow key, string prefix)
  2731. {
  2732. // Start new line
  2733. target.AppendLine();
  2734. // Append prefix if any
  2735. if (!String.IsNullOrEmpty(prefix))
  2736. target.Append(prefix);
  2737. // Start definition
  2738. target.Append("\nCONSTRAINT ");
  2739. QueryBuilder.WriteIdentifier(key, ForeignKey.Name, target);
  2740. target.Append("\n\tFOREIGN KEY ");
  2741. QueryBuilder.WriteIdentifier(key, ForeignKey.Name, target);
  2742. // Extract all foreign keys columns
  2743. DataRow[] columns = DataInterpreter.Select(
  2744. ForeignKeysColumns,
  2745. ForeignKeyColumn.ForeignKeyName,
  2746. DataInterpreter.GetStringNotNull(key, ForeignKey.Name),
  2747. ForeignKeyColumn.OrdinalPosition);
  2748. // Validate array
  2749. if (columns == null)
  2750. {
  2751. Debug.Fail("Failed to get foreign key columns!");
  2752. return;
  2753. }
  2754. // Write source columns
  2755. WriteColumnNames(target, columns, ForeignKeyColumn.Name);
  2756. // Write refernces
  2757. target.Append("\n\tREFERENCES ");
  2758. QueryBuilder.WriteIdentifier(key, ForeignKey.ReferencedTableName, target);
  2759. // Write referenced columns
  2760. WriteColumnNames(target, columns, ForeignKeyColumn.ReferencedColumn);
  2761. // Write options
  2762. QueryBuilder.WriteIfNotEmptyString(key, ForeignKey.OnDelete, "\n\tON DELETE ", target, false);
  2763. QueryBuilder.WriteIfNotEmptyString(key, ForeignKey.OnUpdate, "\n\tON UPDATE ", target, false);
  2764. // Write trailing ','
  2765. target.Append(",");
  2766. }
  2767. /// <summary>
  2768. /// Writes list of foreign key columns names into query. Can write both source
  2769. /// columns and referenced columns names.
  2770. /// </summary>
  2771. /// <param name="target">String builder, used to build query.</param>
  2772. /// <param name="columns">DataRow array with foreign key columns.</param>
  2773. /// <param name="attributeName">Name of the column attribute to write to query.</param>
  2774. private static void WriteColumnNames(StringBuilder target, DataRow[] columns, string attributeName)
  2775. {
  2776. target.Append(" (");
  2777. for (int i = 0; i < columns.Length; i++)
  2778. {
  2779. if (i > 0)
  2780. target.Append(", ");
  2781. QueryBuilder.WriteIdentifier(columns[i], attributeName, target);
  2782. }
  2783. target.Append(")");
  2784. }
  2785. /// <summary>
  2786. /// Returns true if given foreign key was changed.
  2787. /// </summary>
  2788. /// <param name="key">DataRow with description of the foreign key.</param>
  2789. /// <returns>Returns true if given foreign key was changed.</returns>
  2790. private bool HasForeignKeyChanged(DataRow key)
  2791. {
  2792. // Check key for changes
  2793. if (DataInterpreter.HasChanged(key))
  2794. return true;
  2795. // Get list of all foreign keys columns
  2796. DataRow[] columns = DataInterpreter.Select(
  2797. ForeignKeysColumns,
  2798. ForeignKeyColumn.ForeignKeyName,
  2799. DataInterpreter.GetStringNotNull(key, ForeignKey.Name));
  2800. // Check columns
  2801. if( columns == null )
  2802. {
  2803. Debug.Fail("Failed to get foreign key columns!");
  2804. return false;
  2805. }
  2806. // Check each column
  2807. foreach (DataRow column in columns)
  2808. if (HasForeignKeyColumnChanged(column))
  2809. return true;
  2810. // Look for deleted columns
  2811. string oldName = DataInterpreter.GetStringNotNull(key, ForeignKey.Name, DataRowVersion.Original);
  2812. foreach (DataRow fkColumn in ForeignKeysColumns.Rows)
  2813. {
  2814. // Only deleted columns are intrusting
  2815. if (fkColumn.RowState != DataRowState.Deleted)
  2816. continue;
  2817. // Extract original foreign key name
  2818. string fkName = DataInterpreter.GetStringNotNull(fkColumn, ForeignKeyColumn.ForeignKeyName, DataRowVersion.Original);
  2819. // If exact like current foreign key name, then it is changed
  2820. if (DataInterpreter.CompareInvariant(oldName, fkName))
  2821. return true;
  2822. }
  2823. return false;
  2824. }
  2825. /// <summary>
  2826. /// Returns true if given foreign key column was changed.
  2827. /// </summary>
  2828. /// <param name="column">DataRow with the foreign key column description.</param>
  2829. /// <returns>Returns true if given foreign key column was changed.</returns>
  2830. private bool HasForeignKeyColumnChanged(DataRow column)
  2831. {
  2832. // Even if only column name was changed, we need to drop and recreate key.
  2833. // See http://bugs.mysql.com/bug.php?id=8377 for details.
  2834. return DataInterpreter.HasChanged(column);
  2835. }
  2836. #endregion
  2837. #region Primary Key SQL operations
  2838. /// <summary>
  2839. /// Check if primary key should be dropped and/or created.
  2840. ///
  2841. /// Primary key should be dropped if:
  2842. /// - Column was excluded from primary key, but not from the table.
  2843. /// - Column auto increment status was changed.
  2844. /// - New column is added to the primary key and it already contains columns.
  2845. ///
  2846. /// Primary key should be created if:
  2847. /// - New column added to the primary key.
  2848. /// - Primary key need to be dropped, but it is not empty.
  2849. ///
  2850. /// Finaly, primary key should be recreated if index columns were reordered.
  2851. /// </summary>
  2852. /// <param name="needDropKey">Indicates that primary key should be droped.</param>
  2853. /// <param name="needNewKey">Indicates if primary key should be added.</param>
  2854. private void DetermineRequiredPrimaryKeyChanges(out bool needDropKey, out bool needNewKey)
  2855. {
  2856. // Set to true if any of primary key columns should be removed
  2857. // from the primary key, but not from the table, or primary
  2858. // key should be removed for other reason.
  2859. needDropKey = false;
  2860. // Set to true if new columns should be added to the primary key
  2861. needNewKey = false;
  2862. // Set true if primary key is exists and need to be keeped
  2863. bool needKeepKey = false;
  2864. // Look for primary key option changes
  2865. foreach (DataRow column in Columns.Rows)
  2866. {
  2867. switch (column.RowState)
  2868. {
  2869. // New column added.
  2870. case DataRowState.Added:
  2871. // Should it be added to primary key?
  2872. needNewKey = needNewKey || DataInterpreter.GetSqlBool(column, Column.IsPrimaryKey).IsTrue;
  2873. break;
  2874. // Old column founded
  2875. case DataRowState.Modified:
  2876. case DataRowState.Unchanged:
  2877. // Its primary key status was changed
  2878. if (DataInterpreter.HasChanged(column, Column.IsPrimaryKey))
  2879. {
  2880. // Should we include it into primary key?
  2881. needNewKey = needNewKey
  2882. || DataInterpreter.GetSqlBool(column, Column.IsPrimaryKey).IsTrue;
  2883. // Should we exclude it from the primary key?
  2884. needDropKey = needDropKey
  2885. || DataInterpreter.GetSqlBool(column, Column.IsPrimaryKey, DataRowVersion.Original).IsTrue;
  2886. }
  2887. // Its primary key status wasn't changed
  2888. else
  2889. {
  2890. // Should we keep it in the primary key?
  2891. needKeepKey = needKeepKey
  2892. || DataInterpreter.GetSqlBool(column, Column.IsPrimaryKey).IsTrue
  2893. || DataInterpreter.GetSqlBool(column, Column.IsAutoIncrement).IsTrue;
  2894. // If column auto increment flag was changed, we need to recreate key
  2895. needDropKey = needDropKey || DataInterpreter.HasChanged(column, Column.IsAutoIncrement);
  2896. }
  2897. break;
  2898. default:
  2899. break;
  2900. }
  2901. // Need new or need to recreate
  2902. needNewKey = needNewKey || (needDropKey && needKeepKey);
  2903. // Need drop or need to extend
  2904. needDropKey = needDropKey || (needNewKey && needKeepKey);
  2905. // Both operations should be performed, quit
  2906. if (needDropKey && needNewKey)
  2907. return;
  2908. }
  2909. // If no operation supposed, check index for changes
  2910. if (!needDropKey && !needNewKey)
  2911. {
  2912. // Search for index
  2913. DataRow index = FindIndex(IndexDescriptor.PRIMARY);
  2914. // If index founded and it is changed, then it means that index columns were
  2915. // reordered, so we need to drop and recreate primary key.
  2916. if (index != null && HasIndexChanged(index))
  2917. {
  2918. needDropKey = true;
  2919. needNewKey = true;
  2920. }
  2921. }
  2922. }
  2923. /// <summary>
  2924. /// Builds table primary key definition.
  2925. /// </summary>
  2926. /// <param name="target">String builder, used to build query.</param>
  2927. /// <param name="prefix">
  2928. /// Prefix to add before definition. Could be empty or "ADD "
  2929. /// </param>
  2930. private void BuildPrimaryKey(StringBuilder target, string prefix)
  2931. {
  2932. // Extract columns, marked as primary key
  2933. DataRow[] primaryKey = DataInterpreter.Select(
  2934. IndexColumns,
  2935. // Select columns which are in primary key
  2936. IndexColumn.Index, IndexDescriptor.PRIMARY,
  2937. // Ensure that auto increment column is the first one.
  2938. IndexColumn.Ordinal );
  2939. // Check if primary key not empty
  2940. if (primaryKey == null || primaryKey.Length <= 0)
  2941. return;
  2942. // Start primary key definition
  2943. target.AppendLine();
  2944. if (!String.IsNullOrEmpty(prefix))
  2945. target.Append(prefix);
  2946. target.Append("PRIMARY KEY (");
  2947. // Write column names
  2948. for (int i = 0; i < primaryKey.Length; i++)
  2949. {
  2950. if (i > 0) target.Append(", ");
  2951. QueryBuilder.WriteIdentifier(primaryKey[i], IndexColumn.Name, target);
  2952. }
  2953. // End primary key definition
  2954. target.Append("),");
  2955. }
  2956. /// <summary>
  2957. /// Returns true if table already have primary key and false otherwise.
  2958. /// </summary>
  2959. private bool HasPrimaryKey
  2960. {
  2961. get
  2962. {
  2963. // Extract columns, marked as primary key
  2964. DataRow[] primaryKey = DataInterpreter.Select(Columns, Column.IsPrimaryKey, DataInterpreter.True);
  2965. return primaryKey != null && primaryKey.Length > 0;
  2966. }
  2967. }
  2968. #endregion
  2969. #region Indexes SQL generation
  2970. /// <summary>
  2971. /// Build definitions changes in the indexes.
  2972. /// </summary>
  2973. /// <param name="target">String builder, used to build query.</param>
  2974. /// <param name="prefix">
  2975. /// Prefix to add before definition. Could be empty or "ADD "
  2976. /// </param>
  2977. private void BuildIndexes(StringBuilder target, string prefix)
  2978. {
  2979. // Iterate through indexes
  2980. foreach (DataRow index in Indexes.Rows)
  2981. {
  2982. // Check index row state
  2983. switch (index.RowState)
  2984. {
  2985. // Create definition for new index
  2986. case DataRowState.Added:
  2987. BuildAddIndex(target, index, prefix);
  2988. break;
  2989. // Create drop index command
  2990. case DataRowState.Deleted:
  2991. BuildDropIndex(target, index);
  2992. break;
  2993. // Create DROP/ADD index, if needed
  2994. default:
  2995. if (HasIndexChanged(index))
  2996. {
  2997. BuildDropIndex(target, index);
  2998. BuildAddIndex(target, index, prefix);
  2999. }
  3000. break;
  3001. }
  3002. }
  3003. }
  3004. /// <summary>
  3005. /// Builds DROP INDEX statement
  3006. /// </summary>
  3007. /// <param name="target">String builder, used to build query.</param>
  3008. /// <param name="index">DataRow with index data.</param>
  3009. private void BuildDropIndex(StringBuilder target, DataRow index)
  3010. {
  3011. // Skip primary key, it will be processed separately
  3012. string kind = DataInterpreter.GetStringNotNull(index, Index.IndexKind, DataRowVersion.Original);
  3013. if (DataInterpreter.CompareInvariant(kind, IndexDescriptor.PRIMARY))
  3014. return;
  3015. target.Append("\nDROP INDEX ");
  3016. QueryBuilder.WriteOldIdentifier(index, Index.Name, target);
  3017. // Write trailing ","
  3018. target.Append(",");
  3019. }
  3020. /// <summary>
  3021. /// Builds ADD INDEX statement
  3022. /// </summary>
  3023. /// <param name="target">String builder, used to build query.</param>
  3024. /// <param name="index">DataRow with index data.</param>
  3025. /// <param name="prefix">
  3026. /// Prefix to add before definition. Could be empty or "ADD "
  3027. /// </param>
  3028. private void BuildAddIndex(StringBuilder target, DataRow index, string prefix)
  3029. {
  3030. // Skip primary key, it will be processed separately
  3031. string kind = DataInterpreter.GetStringNotNull(index, Index.IndexKind);
  3032. if (DataInterpreter.CompareInvariant(kind, IndexDescriptor.PRIMARY))
  3033. return;
  3034. // Write new line
  3035. target.AppendLine();
  3036. // Write prefix if any
  3037. if (!String.IsNullOrEmpty(prefix))
  3038. target.Append(prefix);
  3039. // Writing index kind
  3040. QueryBuilder.WriteValue(kind, target, false);
  3041. // If index kind is not INDEX, write INDEX
  3042. if (!DataInterpreter.CompareInvariant(kind, IndexDescriptor.INDEX))
  3043. {
  3044. // Append space
  3045. target.Append(' ');
  3046. QueryBuilder.WriteValue(IndexDescriptor.INDEX, target, false);
  3047. }
  3048. // Append space
  3049. target.Append(' ');
  3050. // Write index name and append space
  3051. QueryBuilder.WriteIdentifier(index, Index.Name, target);
  3052. // For UNIQUE and INDEX indexes index type is available
  3053. if (DataInterpreter.CompareInvariant(kind, IndexDescriptor.INDEX)
  3054. || DataInterpreter.CompareInvariant(kind, IndexDescriptor.UNIQUE))
  3055. QueryBuilder.WriteIfNotEmptyString(index, Index.IndexType, " USING ", target, false);
  3056. // Extract all index columns
  3057. DataRow[] columns = DataInterpreter.Select(
  3058. IndexColumns,
  3059. IndexColumn.Index,
  3060. DataInterpreter.GetStringNotNull(index, Index.Name),
  3061. IndexColumn.Ordinal);
  3062. // Validate array
  3063. if (columns == null)
  3064. {
  3065. Debug.Fail("Failed to get index columns!");
  3066. return;
  3067. }
  3068. // Write source columns
  3069. target.Append(" (");
  3070. for (int i = 0; i < columns.Length; i++)
  3071. {
  3072. // Write comma if not the first column
  3073. if (i > 0)
  3074. target.Append(", ");
  3075. // Write column name
  3076. QueryBuilder.WriteIdentifier(columns[i], IndexColumn.Name, target);
  3077. // Get index length and write it if not null
  3078. Nullable<Int64> length = DataInterpreter.GetInt(columns[i], IndexColumn.IndexLength);
  3079. if (length != null)
  3080. target.AppendFormat(CultureInfo.InvariantCulture, "({0})", length.Value);
  3081. }
  3082. target.Append(")");
  3083. // Write trailing ','
  3084. target.Append(",");
  3085. }
  3086. /// <summary>
  3087. /// Returns true if given index was changed.
  3088. /// </summary>
  3089. /// <param name="index">DataRow with description of the index.</param>
  3090. /// <returns>Returns true if given index was changed.</returns>
  3091. private bool HasIndexChanged(DataRow index)
  3092. {
  3093. // Check index for changes
  3094. if (DataInterpreter.HasChanged(index))
  3095. return true;
  3096. // Get list of all index columns
  3097. DataRow[] columns = DataInterpreter.Select(
  3098. IndexColumns,
  3099. IndexColumn.Index,
  3100. DataInterpreter.GetStringNotNull(index, Index.Name));
  3101. // Check columns
  3102. if (columns == null)
  3103. {
  3104. Debug.Fail("Failed to get index columns!");
  3105. return false;
  3106. }
  3107. // Check each column
  3108. foreach (DataRow column in columns)
  3109. if (HasIndexColumnChanged(column))
  3110. return true;
  3111. // Look for deleted columns
  3112. string oldName = DataInterpreter.GetStringNotNull(index, Index.Name, DataRowVersion.Original);
  3113. foreach (DataRow indexColumn in IndexColumns.Rows)
  3114. {
  3115. // Only deleted columns are intrusting
  3116. if (indexColumn.RowState != DataRowState.Deleted)
  3117. continue;
  3118. // Extract original index name
  3119. string indexName = DataInterpreter.GetStringNotNull(indexColumn, IndexColumn.Index, DataRowVersion.Original);
  3120. // If exact like current idex name, then it is changed
  3121. if (DataInterpreter.CompareInvariant(oldName, indexName))
  3122. return true;
  3123. }
  3124. return false;
  3125. }
  3126. /// <summary>
  3127. /// Returns true if given index column was changed.
  3128. /// </summary>
  3129. /// <param name="column">DataRow with the index column description.</param>
  3130. /// <returns>Returns true if given index column was changed.</returns>
  3131. private bool HasIndexColumnChanged(DataRow column)
  3132. {
  3133. // If changed something besides source column name, return true.
  3134. if (column.RowState == DataRowState.Deleted
  3135. || column.RowState == DataRowState.Added
  3136. || DataInterpreter.HasChanged(column, IndexColumnName) )
  3137. return true;
  3138. // If source column name is unchanged, return false.
  3139. if (!DataInterpreter.HasChanged(column, IndexColumn.Name))
  3140. return false;
  3141. // Get new column name
  3142. string newColumnName = DataInterpreter.GetStringNotNull(column, IndexColumn.Name);
  3143. string oldColumnName = DataInterpreter.GetStringNotNull(column, IndexColumn.Name, DataRowVersion.Original);
  3144. if (String.IsNullOrEmpty(newColumnName))
  3145. {
  3146. Debug.Fail("Failed to get column names!");
  3147. return false;
  3148. }
  3149. // Get source column description
  3150. DataRow sourceColumn = FindColumn(newColumnName);
  3151. if (sourceColumn == null)
  3152. {
  3153. Debug.Fail("Unable to get source column description!");
  3154. return false;
  3155. }
  3156. // If column is new, then return true
  3157. if (sourceColumn.RowState == DataRowState.Added)
  3158. return true;
  3159. // If column is deleted, when it is error
  3160. if (sourceColumn.RowState == DataRowState.Deleted)
  3161. {
  3162. Debug.Fail("Deleted column attached to index!");
  3163. return false;
  3164. }
  3165. // Get old name in column
  3166. string oldNameInColumn = DataInterpreter.GetStringNotNull(sourceColumn, Column.Name, DataRowVersion.Original);
  3167. if (String.IsNullOrEmpty(oldNameInColumn))
  3168. {
  3169. Debug.Fail("Failed to get old name for source column!");
  3170. return false;
  3171. }
  3172. // Compare names (if they are equals, then index column should be considered as unchanged)
  3173. return !DataInterpreter.CompareInvariant(oldColumnName, oldNameInColumn);
  3174. }
  3175. #endregion
  3176. #region Handling attribute changes
  3177. /// <summary>
  3178. /// Handles table attributes changes.
  3179. /// </summary>
  3180. protected override void HandleAttributesChanges()
  3181. {
  3182. base.HandleAttributesChanges();
  3183. // If caharacter set changed, need to change collation to default
  3184. // for new character set.
  3185. if (IsAttributeChanged(Table.CharacterSet))
  3186. HandleCharacterSetChanged();
  3187. }
  3188. /// <summary>
  3189. /// Reset collation to default if character set was changed.
  3190. /// </summary>
  3191. private void HandleCharacterSetChanged()
  3192. {
  3193. if (!DataInterpreter.CompareInvariant(
  3194. previousCharacterSet,
  3195. CharacterSet))
  3196. {
  3197. previousCharacterSet = CharacterSet;
  3198. Collation = Connection.GetDefaultCollationForCharacterSet(CharacterSet);
  3199. }
  3200. }
  3201. // We don't know what attribute exactly was changed at this moment
  3202. // so, we need to store previously processed values.
  3203. #region Support private variables
  3204. /// <summary>
  3205. /// Used to store previously porcessed character set name. DataRow.Original
  3206. /// is not enough.
  3207. /// </summary>
  3208. private string previousCharacterSet = String.Empty;
  3209. #endregion
  3210. #endregion
  3211. #region Aditional private methods
  3212. /// <summary>
  3213. /// Returns DataRow for column with given name.
  3214. /// </summary>
  3215. /// <param name="columnName">Name of column to search.</param>
  3216. /// <returns>Returns DataRow for column with given name.</returns>
  3217. private DataRow FindColumn(string columnName)
  3218. {
  3219. return Columns.Rows.Find(new object[] { Schema, OldName, columnName });
  3220. }
  3221. /// <summary>
  3222. /// Returns DataRow for foreign key with given name.
  3223. /// </summary>
  3224. /// <param name="keyName">Name of foreign key to search.</param>
  3225. /// <returns>Returns DataRow for foreign key with given name.</returns>
  3226. private DataRow FindForeignKey(string keyName)
  3227. {
  3228. return ForeignKeys.Rows.Find(new object[] { Schema, OldName, keyName });
  3229. }
  3230. /// <summary>
  3231. /// Returns DataRow for index with given name.
  3232. /// </summary>
  3233. /// <param name="indexName">Name of index to search.</param>
  3234. /// <returns>Returns DataRow for index with given name.</returns>
  3235. private DataRow FindIndex(string indexName)
  3236. {
  3237. return Indexes.Rows.Find(new object[] { Schema, OldName, indexName });
  3238. }
  3239. /// <summary>
  3240. /// Returns DataRow for index column with given name.
  3241. /// </summary>
  3242. /// <param name="indexName">Name of index to search.</param>
  3243. /// <param name="columnName">Name of column to search.</param>
  3244. /// <returns>Returns DataRow for index with given name.</returns>
  3245. private DataRow FindIndexColumn(string indexName, string columnName)
  3246. {
  3247. return IndexColumns.Rows.Find(new object[] { Schema, OldName, indexName, columnName });
  3248. }
  3249. /// <summary>
  3250. /// Returns copy of given table where all data rows have state Added.
  3251. /// </summary>
  3252. /// <param name="table">DataTablw to copy.</param>
  3253. /// <returns>Returns copy of given table where all data rows have state Added.</returns>
  3254. private DataTable MakeCopy(DataTable table)
  3255. {
  3256. // Clone structure
  3257. DataTable result = table.Clone();
  3258. // Copy data
  3259. foreach (DataRow row in table.Rows)
  3260. result.Rows.Add(row.ItemArray);
  3261. return result;
  3262. }
  3263. /// <summary>
  3264. /// Sets correct table name for all rows to support Find methods.
  3265. /// </summary>
  3266. /// <param name="table">DataTablw to process.</param>
  3267. /// <param name="attribute">Name of table name attribute.</param>
  3268. private void ResetTableName(DataTable table, string attribute)
  3269. {
  3270. foreach (DataRow row in table.Rows)
  3271. DataInterpreter.SetValueIfChanged(row, attribute, OldName);
  3272. }
  3273. /// <summary>
  3274. /// Generates new name for cloned foreign key to avoid conflicts. Some
  3275. /// times InnoDB fails if there is another key with same name in the other
  3276. /// table.
  3277. /// </summary>
  3278. /// <param name="key">Foreign key row to generate new name for.</param>
  3279. private void GenerateNewName(DataRow key)
  3280. {
  3281. // Extract name
  3282. string name = DataInterpreter.GetStringNotNull(key, ForeignKey.Name);
  3283. // Select foreign key columns (from old table because not copied yet)
  3284. DataRow[] columns = DataInterpreter.Select(ForeignKeysColumns, ForeignKeyColumn.ForeignKeyName, name);
  3285. // Generate new name template
  3286. string template = name + "_";
  3287. // Identifier to complete
  3288. object[] id = new object[] { null, Schema, null /*for all tables*/, null };
  3289. // Complete identifier
  3290. ObjectDescriptor.CompleteNewObjectID(Hierarchy, ForeignKeyDescriptor.TypeName, ref id, template);
  3291. // Set new name for key
  3292. DataInterpreter.SetValueIfChanged(key, ForeignKey.Name, id[3]);
  3293. // Cahnge key name for each column
  3294. foreach (DataRow column in columns)
  3295. DataInterpreter.SetValueIfChanged(column, ForeignKeyColumn.ForeignKeyName, id[3]);
  3296. }
  3297. /// <summary>
  3298. /// Makes given column first in the primary key index.
  3299. /// </summary>
  3300. /// <param name="firstColumn">Column to make first in the primary key index.</param>
  3301. private void MakeFirstPrimaryKeyColumn(DataRow firstColumn)
  3302. {
  3303. // Set this index column to be the first
  3304. int ordinal = 1;
  3305. DataInterpreter.SetValueIfChanged(firstColumn, IndexColumn.Ordinal, ordinal++);
  3306. // Select all primary key columns
  3307. DataRow[] columns = DataInterpreter.Select(
  3308. IndexColumns,
  3309. IndexColumn.Index,
  3310. IndexDescriptor.PRIMARY,
  3311. IndexColumn.Ordinal);
  3312. // Validate enumerated columns
  3313. if (columns == null)
  3314. {
  3315. Debug.Fail("Failed to enumerate primary key columns!");
  3316. return;
  3317. }
  3318. // Set new ordinal for each column
  3319. foreach (DataRow column in columns)
  3320. {
  3321. // Skip first column
  3322. if (column == firstColumn)
  3323. continue;
  3324. // Set new ordinal
  3325. DataInterpreter.SetValueIfChanged(column, IndexColumn.Ordinal, ordinal++);
  3326. }
  3327. }
  3328. /// <summary>
  3329. /// Calls base class SaveFailed method. Switch names before and after call to prevent
  3330. /// editor caption changes.
  3331. /// </summary>
  3332. private void CallBaseSaveFailed()
  3333. {
  3334. string newName = Name;
  3335. Name = OldName;
  3336. base.SaveFailed();
  3337. Name = newName;
  3338. }
  3339. #endregion
  3340. #region Private variables to store properties
  3341. private DataTable columnsTable;
  3342. private DataTable foreignKeysTable;
  3343. private DataTable foreignKeysColumnsTable;
  3344. private DataTable indexesTable;
  3345. private DataTable indexColumnsTable;
  3346. private Dictionary<string, object> columnDefaults = new Dictionary<string,object>();
  3347. #endregion
  3348. #region ICharacterSetProvider Members
  3349. /// <summary>
  3350. /// Returns character set to select default collations.
  3351. /// </summary>
  3352. string CollationConverter.ICharacterSetProvider.CharcterSet
  3353. {
  3354. get
  3355. {
  3356. return this.CharacterSet;
  3357. }
  3358. }
  3359. #endregion
  3360. #region Private constants
  3361. private static string[] IndexColumnName = new string[] { IndexColumn.Name };
  3362. #endregion
  3363. }
  3364. }