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