1 #region MIT license
2 //
3 // MIT license
4 //
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy
8 // of this software and associated documentation files (the "Software"), to deal
9 // in the Software without restriction, including without limitation the rights
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 // copies of the Software, and to permit persons to whom the Software is
12 // furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 // THE SOFTWARE.
24 //
25 #endregion
26 using System;
27 using System.Collections.Generic;
28 using System.Data;
29 using System.IO;
30 using System.Linq;
31 
32 #if MONO_STRICT
33 using System.Data.Linq;
34 #else
35 using DbLinq.Data.Linq;
36 #endif
37 
38 using DbLinq.Factory;
39 using DbLinq.Schema;
40 using DbLinq.Schema.Dbml;
41 using System.Text.RegularExpressions;
42 
43 namespace DbLinq.Vendor.Implementation
44 {
45 #if !MONO_STRICT
46     public
47 #endif
48     abstract partial class SchemaLoader : ISchemaLoader
49     {
50         /// <summary>
51         /// Underlying vendor
52         /// </summary>
53         /// <value></value>
54         public abstract IVendor Vendor { get; set; }
55         /// <summary>
56         /// Connection used to read schema
57         /// </summary>
58         /// <value></value>
59         public IDbConnection Connection { get; set; }
60         /// <summary>
61         /// Gets or sets the name formatter.
62         /// </summary>
63         /// <value>The name formatter.</value>
64         public INameFormatter NameFormatter { get; set; }
65 
66         private TextWriter log;
67         /// <summary>
68         /// Log output
69         /// </summary>
70         public TextWriter Log
71         {
72             get { return log ?? Console.Out; }
73             set { log = value; }
74         }
75 
76         /// <summary>
77         /// Loads database schema
78         /// </summary>
79         /// <param name="databaseName"></param>
80         /// <param name="nameAliases"></param>
81         /// <param name="nameFormat"></param>
82         /// <param name="loadStoredProcedures"></param>
83         /// <param name="contextNamespace"></param>
84         /// <param name="entityNamespace"></param>
85         /// <returns></returns>
Load(string databaseName, INameAliases nameAliases, NameFormat nameFormat, bool loadStoredProcedures, string contextNamespace, string entityNamespace)86         public virtual Database Load(string databaseName, INameAliases nameAliases, NameFormat nameFormat,
87             bool loadStoredProcedures, string contextNamespace, string entityNamespace)
88         {
89             // check if connection is open. Note: we may use something more flexible
90             if (Connection.State != ConnectionState.Open)
91                 Connection.Open();
92 
93             // get the database name. If we don't have one, take it from connection string...
94             if (string.IsNullOrEmpty(databaseName))
95                 databaseName = Connection.Database;
96             // ... and if connection string doesn't provide a name, then throw an error
97             if (string.IsNullOrEmpty(databaseName))
98                 throw new ArgumentException("A database name is required. Please specify /database=<databaseName>");
99 
100             databaseName = GetDatabaseNameAliased(databaseName, nameAliases);
101 
102             var schemaName = NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat);
103             var names = new Names();
104             var schema = new Database
105                              {
106                                  Name = schemaName.DbName,
107                                  Class = GetRuntimeClassName(schemaName.ClassName, nameAliases),
108                                  BaseType = typeof(DataContext).FullName,
109                                  ContextNamespace = contextNamespace,
110                                  EntityNamespace = entityNamespace,
111                              };
112 
113             // order is important, we must have:
114             // 1. tables
115             // 2. columns
116             // 3. constraints
117             LoadTables(schema, schemaName, Connection, nameAliases, nameFormat, names);
118             LoadColumns(schema, schemaName, Connection, nameAliases, nameFormat, names);
119             CheckColumnsName(schema);
120             LoadConstraints(schema, schemaName, Connection, nameFormat, names);
121             CheckConstraintsName(schema);
122             if (loadStoredProcedures)
123                 LoadStoredProcedures(schema, schemaName, Connection, nameFormat);
124             // names aren't checked here anymore, because this confuses DBML editor.
125             // they will (for now) be checked before .cs generation
126             // in the end, when probably will end up in mapping source (or somewhere around)
127             //CheckNamesSafety(schema);
128 
129             // generate backing fields name (since we have here correct names)
130             GenerateStorageAndMemberFields(schema);
131 
132             return schema;
133         }
134 
135         /// <summary>
136         /// Gets a usable name for the database.
137         /// </summary>
138         /// <param name="databaseName">Name of the database.</param>
139         /// <returns></returns>
GetDatabaseName(string databaseName)140         protected virtual string GetDatabaseName(string databaseName)
141         {
142             return databaseName;
143         }
144 
GetDatabaseNameAliased(string databaseName, INameAliases nameAliases)145         protected virtual string GetDatabaseNameAliased(string databaseName, INameAliases nameAliases)
146         {
147             string databaseNameAliased = nameAliases != null ? nameAliases.GetDatabaseNameAlias(databaseName) : null;
148             return (databaseNameAliased != null) ? databaseNameAliased : GetDatabaseName(databaseName);
149         }
150 
151         /// <summary>
152         /// Gets a usable name for the database class.
153         /// </summary>
154         /// <param name="databaseName">Name of the clas.</param>
155         /// <returns></returns>
GetRuntimeClassName(string className, INameAliases nameAliases)156         protected virtual string GetRuntimeClassName(string className, INameAliases nameAliases)
157         {
158             string classNameAliased = nameAliases != null ? nameAliases.GetClassNameAlias(className) : null;
159             return (classNameAliased != null) ? classNameAliased : className;
160         }
161 
162         /// <summary>
163         /// Writes an error line.
164         /// </summary>
165         /// <param name="format">The format.</param>
166         /// <param name="arg">The arg.</param>
WriteErrorLine(string format, params object[] arg)167         protected void WriteErrorLine(string format, params object[] arg)
168         {
169             var o = Log;
170             if (o == Console.Out)
171                 o = Console.Error;
172             o.WriteLine(format, arg);
173         }
174 
SchemaLoader()175         protected SchemaLoader()
176         {
177             NameFormatter = ObjectFactory.Create<INameFormatter>(); // the Pluralize property is set dynamically, so no singleton
178         }
179 
180         /// <summary>
181         /// Gets the extraction type from a columnname.
182         /// </summary>
183         /// <param name="dbColumnName">Name of the db column.</param>
184         /// <returns></returns>
GetExtraction(string dbColumnName)185         protected virtual WordsExtraction GetExtraction(string dbColumnName)
186         {
187             bool isMixedCase = dbColumnName != dbColumnName.ToLower() && dbColumnName != dbColumnName.ToUpper();
188             return isMixedCase ? WordsExtraction.FromCase : WordsExtraction.FromDictionary;
189         }
190 
191         /// <summary>
192         /// Gets the full name of a name and schema.
193         /// </summary>
194         /// <param name="dbName">Name of the db.</param>
195         /// <param name="dbSchema">The db schema.</param>
196         /// <returns></returns>
GetFullDbName(string dbName, string dbSchema)197         protected virtual string GetFullDbName(string dbName, string dbSchema)
198         {
199             string fullDbName;
200             if (dbSchema == null)
201                 fullDbName = dbName;
202             else
203                 fullDbName = string.Format("{0}.{1}", dbSchema, dbName);
204             return fullDbName;
205         }
206 
207         /// <summary>
208         /// Creates the name of the table given a name and schema
209         /// </summary>
210         /// <param name="dbTableName">Name of the db table.</param>
211         /// <param name="dbSchema">The db schema.</param>
212         /// <param name="nameAliases">The name aliases.</param>
213         /// <param name="nameFormat">The name format.</param>
214         /// <param name="extraction">The extraction.</param>
215         /// <returns></returns>
CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat, WordsExtraction extraction)216         protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat, WordsExtraction extraction)
217         {
218             // if we have an alias, use it, and don't try to analyze it (a human probably already did the job)
219             var tableTypeAlias = nameAliases != null ? nameAliases.GetTableTypeAlias(dbTableName, dbSchema) : null;
220             if (tableTypeAlias != null)
221                 extraction = WordsExtraction.None;
222             else
223                 tableTypeAlias = dbTableName;
224 
225             var tableName = NameFormatter.GetTableName(tableTypeAlias, extraction, nameFormat);
226 
227             // alias for member
228             var tableMemberAlias = nameAliases != null ? nameAliases.GetTableMemberAlias(dbTableName, dbSchema) : null;
229             if (tableMemberAlias != null)
230                 tableName.MemberName = tableMemberAlias;
231 
232             tableName.DbName = GetFullDbName(dbTableName, dbSchema);
233             return tableName;
234         }
235 
CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)236         protected virtual TableName CreateTableName(string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)
237         {
238             return CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat, GetExtraction(dbTableName));
239         }
240 
241         Regex startsWithNumber = new Regex(@"^\d", RegexOptions.Compiled);
242 
243         /// <summary>
244         /// Creates the name of the column.
245         /// </summary>
246         /// <param name="dbColumnName">Name of the db column.</param>
247         /// <param name="dbTableName">Name of the db table.</param>
248         /// <param name="dbSchema">The db schema.</param>
249         /// <param name="nameAliases">The name aliases.</param>
250         /// <param name="nameFormat">The name format.</param>
251         /// <returns></returns>
CreateColumnName(string dbColumnName, string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)252         protected virtual ColumnName CreateColumnName(string dbColumnName, string dbTableName, string dbSchema, INameAliases nameAliases, NameFormat nameFormat)
253         {
254             var columnNameAlias = nameAliases != null ? nameAliases.GetColumnMemberAlias(dbColumnName, dbTableName, dbSchema) : null;
255             WordsExtraction extraction;
256             if (columnNameAlias != null)
257             {
258                 extraction = WordsExtraction.None;
259             }
260             else
261             {
262                 extraction = GetExtraction(dbColumnName);
263                 columnNameAlias = dbColumnName;
264             }
265             var columnName = NameFormatter.GetColumnName(columnNameAlias, extraction, nameFormat);
266             // The member name can not be the same as the class
267             // we add a "1" (just like SqlMetal does)
268             var tableName = CreateTableName(dbTableName, dbSchema, nameAliases, nameFormat);
269             if (columnName.PropertyName == tableName.ClassName)
270                 columnName.PropertyName = columnName.PropertyName + "1";
271 
272             if (startsWithNumber.IsMatch(columnName.PropertyName))
273                 columnName.PropertyName = "_" + columnName.PropertyName;
274 
275             columnName.DbName = dbColumnName;
276             return columnName;
277         }
278 
279         /// <summary>
280         /// Creates the name of the procedure.
281         /// </summary>
282         /// <param name="dbProcedureName">Name of the db procedure.</param>
283         /// <param name="dbSchema">The db schema.</param>
284         /// <param name="nameFormat">The name format.</param>
285         /// <returns></returns>
CreateProcedureName(string dbProcedureName, string dbSchema, NameFormat nameFormat)286         protected virtual ProcedureName CreateProcedureName(string dbProcedureName, string dbSchema, NameFormat nameFormat)
287         {
288             var procedureName = NameFormatter.GetProcedureName(dbProcedureName, GetExtraction(dbProcedureName), nameFormat);
289             procedureName.DbName = GetFullDbName(dbProcedureName, dbSchema);
290             return procedureName;
291         }
292 
293         /// <summary>
294         /// Creates the name of the association.
295         /// </summary>
296         /// <param name="dbManyName">Name of the db many.</param>
297         /// <param name="dbManySchema">The db many schema.</param>
298         /// <param name="dbOneName">Name of the db one.</param>
299         /// <param name="dbOneSchema">The db one schema.</param>
300         /// <param name="dbConstraintName">Name of the db constraint.</param>
301         /// <param name="foreignKeyName">Name of the foreign key.</param>
302         /// <param name="nameFormat">The name format.</param>
303         /// <returns></returns>
CreateAssociationName(string dbManyName, string dbManySchema, string dbOneName, string dbOneSchema, string dbConstraintName, string foreignKeyName, NameFormat nameFormat)304         protected virtual AssociationName CreateAssociationName(string dbManyName, string dbManySchema,
305             string dbOneName, string dbOneSchema, string dbConstraintName, string foreignKeyName, NameFormat nameFormat)
306         {
307             var associationName = NameFormatter.GetAssociationName(dbManyName, dbOneName,
308                 dbConstraintName, foreignKeyName, GetExtraction(dbManyName), nameFormat);
309             associationName.DbName = GetFullDbName(dbManyName, dbManySchema);
310             return associationName;
311         }
312 
313         /// <summary>
314         /// Creates the name of the schema.
315         /// </summary>
316         /// <param name="databaseName">Name of the database.</param>
317         /// <param name="connection">The connection.</param>
318         /// <param name="nameFormat">The name format.</param>
319         /// <returns></returns>
CreateSchemaName(string databaseName, IDbConnection connection, NameFormat nameFormat)320         protected virtual SchemaName CreateSchemaName(string databaseName, IDbConnection connection, NameFormat nameFormat)
321         {
322             if (string.IsNullOrEmpty(databaseName))
323             {
324                 databaseName = connection.Database;
325                 if (string.IsNullOrEmpty(databaseName))
326                     throw new ArgumentException("Could not deduce database name from connection string. Please specify /database=<databaseName>");
327             }
328             return NameFormatter.GetSchemaName(databaseName, GetExtraction(databaseName), nameFormat);
329         }
330 
CreateParameterName(string dbParameterName, NameFormat nameFormat)331         protected virtual ParameterName CreateParameterName(string dbParameterName, NameFormat nameFormat)
332         {
333             var parameterName = NameFormatter.GetParameterName(dbParameterName, GetExtraction(dbParameterName), nameFormat);
334             return parameterName;
335         }
336 
337         protected class Names
338         {
339             public IDictionary<string, TableName> TablesNames = new Dictionary<string, TableName>();
340             public IDictionary<string, IDictionary<string, ColumnName>> ColumnsNames = new Dictionary<string, IDictionary<string, ColumnName>>();
341 
AddColumn(string dbTableName, ColumnName columnName)342             public void AddColumn(string dbTableName, ColumnName columnName)
343             {
344                 IDictionary<string, ColumnName> columns;
345                 if (!ColumnsNames.TryGetValue(dbTableName, out columns))
346                 {
347                     columns = new Dictionary<string, ColumnName>();
348                     ColumnsNames[dbTableName] = columns;
349                 }
350                 columns[columnName.DbName] = columnName;
351             }
352         }
353 
354         /// <summary>
355         /// Loads the tables in the given schema.
356         /// </summary>
357         /// <param name="schema">The schema.</param>
358         /// <param name="schemaName">Name of the schema.</param>
359         /// <param name="conn">The conn.</param>
360         /// <param name="nameAliases">The name aliases.</param>
361         /// <param name="nameFormat">The name format.</param>
362         /// <param name="names">The names.</param>
LoadTables(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)363         protected virtual void LoadTables(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)
364         {
365             var tables = ReadTables(conn, schemaName.DbName);
366             foreach (var row in tables)
367             {
368                 var tableName = CreateTableName(row.Name, row.Schema, nameAliases, nameFormat);
369                 names.TablesNames[tableName.DbName] = tableName;
370 
371                 var table = new Table();
372                 table.Name = tableName.DbName;
373                 table.Member = tableName.MemberName;
374                 table.Type.Name = tableName.ClassName;
375                 schema.Tables.Add(table);
376             }
377         }
378 
379         /// <summary>
380         /// Loads the columns.
381         /// </summary>
382         /// <param name="schema">The schema.</param>
383         /// <param name="schemaName">Name of the schema.</param>
384         /// <param name="conn">The conn.</param>
385         /// <param name="nameAliases">The name aliases.</param>
386         /// <param name="nameFormat">The name format.</param>
387         /// <param name="names">The names.</param>
LoadColumns(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)388         protected void LoadColumns(Database schema, SchemaName schemaName, IDbConnection conn, INameAliases nameAliases, NameFormat nameFormat, Names names)
389         {
390             var columnRows = ReadColumns(conn, schemaName.DbName);
391             foreach (var columnRow in columnRows)
392             {
393                 var columnName = CreateColumnName(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema, nameAliases, nameFormat);
394                 names.AddColumn(columnRow.TableName, columnName);
395 
396                 //find which table this column belongs to
397                 string fullColumnDbName = GetFullDbName(columnRow.TableName, columnRow.TableSchema);
398                 DbLinq.Schema.Dbml.Table tableSchema = schema.Tables.FirstOrDefault(tblSchema => fullColumnDbName == tblSchema.Name);
399                 if (tableSchema == null)
400                 {
401                     WriteErrorLine("ERROR L46: Table '" + columnRow.TableName + "' not found for column " + columnRow.ColumnName);
402                     continue;
403                 }
404                 var column = new Column();
405                 column.Name = columnName.DbName;
406                 column.Member = columnName.PropertyName;
407                 column.DbType = columnRow.FullType;
408 
409                 if (columnRow.PrimaryKey.HasValue)
410                     column.IsPrimaryKey = columnRow.PrimaryKey.Value;
411 
412                 bool? generated = (nameAliases != null) ? nameAliases.GetColumnGenerated(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null;
413                 if (!generated.HasValue)
414                     generated = columnRow.Generated;
415                 if (generated.HasValue)
416                     column.IsDbGenerated = generated.Value;
417 
418                 AutoSync? autoSync = (nameAliases != null) ? nameAliases.GetColumnAutoSync(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null;
419                 if (autoSync.HasValue)
420                     column.AutoSync = autoSync.Value;
421 
422                 // the Expression can originate from two sources:
423                 // 1. DefaultValue
424                 // 2. Expression
425                 // we use any valid source (we can't have both)
426                 if (column.IsDbGenerated && columnRow.DefaultValue != null)
427                     column.Expression = columnRow.DefaultValue;
428 
429                 column.CanBeNull = columnRow.Nullable;
430 
431                 string columnTypeAlias = nameAliases != null ? nameAliases.GetColumnForcedType(columnRow.ColumnName, columnRow.TableName, columnRow.TableSchema) : null;
432                 var columnType = MapDbType(columnName.DbName, columnRow);
433 
434                 var columnEnumType = columnType as EnumType;
435                 if (columnEnumType != null)
436                 {
437                     var enumType = column.SetExtendedTypeAsEnumType();
438                     enumType.Name = columnEnumType.Name;
439                     foreach (KeyValuePair<string, int> enumValue in columnEnumType.EnumValues)
440                     {
441                         enumType[enumValue.Key] = enumValue.Value;
442                     }
443                 }
444                 else if (columnTypeAlias != null)
445                     column.Type = columnTypeAlias;
446                 else
447                     column.Type = columnType.ToString();
448 
449                 tableSchema.Type.Columns.Add(column);
450             }
451         }
452     }
453 }
454