-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathPostgreSqlTablesProvider.cs
More file actions
201 lines (159 loc) · 8.14 KB
/
Copy pathPostgreSqlTablesProvider.cs
File metadata and controls
201 lines (159 loc) · 8.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Dapper;
using DynamicLinqPadPostgreSqlDriver.Extensions;
using LINQPad.Extensibility.DataContext;
namespace DynamicLinqPadPostgreSqlDriver
{
internal class PostgreSqlTablesProvider : IDatabaseObjectProvider
{
private readonly IConnectionInfo _cxInfo;
private readonly ModuleBuilder _moduleBuilder;
private readonly IDbConnection _connection;
private readonly string _nameSpace;
/// <summary>
/// Tables should appear first in Explorer view
/// </summary>
public int Priority { get; } = 0;
public PostgreSqlTablesProvider(IConnectionInfo cxInfo, ModuleBuilder moduleBuilder, IDbConnection connection, string nameSpace)
{
_cxInfo = cxInfo;
_moduleBuilder = moduleBuilder;
_connection = connection;
_nameSpace = nameSpace;
}
public ExplorerItem EmitCodeAndGetExplorerItemTree(TypeBuilder dataContextTypeBuilder)
{
var query = SqlHelper.LoadSql("QueryTables.sql");
var tables = _connection.Query(query);
// The explorer items below the "Tables" explorer items can be either the
// schemas or directly the tables, if only one schema is available (or selected).
var rootExplorerItems = new List<ExplorerItem>();
var schemas = _cxInfo.GetSchemas();
var schemaGroups = schemas.Any()
? tables.GroupBy(t => t.TableSchema).Where(g => schemas.Contains(((string)g.Key).ToLower())).ToArray()
: tables.GroupBy(t => t.TableSchema).ToArray();
// If there is only one schema, then the schema explorer item can be skipped and
// it is not required to add the schema to the table name.
var onlyOneSchema = schemaGroups.Length == 1;
foreach (var schemaGroup in schemaGroups)
{
var tableExplorerItems = new List<ExplorerItem>();
var preparedTables = new List<TableData>();
foreach (var table in schemaGroup.OrderBy(t => t.TableName))
{
var unmodifiedTableName = (string) table.TableName;
var unmodifiedTableSchema = (string) table.TableSchema;
var tableName = onlyOneSchema
? _cxInfo.GetTableName(unmodifiedTableName)
: _cxInfo.GetTableName(unmodifiedTableSchema + "_" + unmodifiedTableName);
var explorerItem = new ExplorerItem(tableName, ExplorerItemKind.QueryableObject, ExplorerIcon.Table)
{
IsEnumerable = true,
Children = new List<ExplorerItem>(),
DragText = tableName,
SqlName = $"{unmodifiedTableSchema}.{unmodifiedTableName}"
};
var tableData = PrepareTableEntity(_cxInfo, _moduleBuilder, _connection, _nameSpace, table.TableCatalog,
unmodifiedTableSchema, unmodifiedTableName, explorerItem);
preparedTables.Add(tableData);
}
// build the associations before the types are created
BuildAssociations(_connection, schemaGroup.Key, preparedTables);
foreach (var tableData in preparedTables)
{
dataContextTypeBuilder.CreateAndAddType(tableData);
tableExplorerItems.Add(tableData.ExplorerItem);
}
if (onlyOneSchema)
{
rootExplorerItems.AddRange(tableExplorerItems);
continue;
}
var schemaExplorerItem = new ExplorerItem(schemaGroup.Key, ExplorerItemKind.Category, ExplorerIcon.Schema)
{
IsEnumerable = true,
Children = tableExplorerItems
};
rootExplorerItems.Add(schemaExplorerItem);
}
return new ExplorerItem("Tables", ExplorerItemKind.Category, ExplorerIcon.Table)
{
IsEnumerable = true,
Children = rootExplorerItems
};
}
private static TableData PrepareTableEntity(IConnectionInfo cxInfo, ModuleBuilder moduleBuilder, IDbConnection dbConnection, string nameSpace, string databaseName, string tableSchema, string tableName, ExplorerItem tableExplorerItem)
{
// get primary key columns
var primaryKeyColumns = dbConnection.GetPrimaryKeyColumns(tableSchema, tableName);
var typeName = $"{nameSpace}.{tableSchema}.{cxInfo.GetTypeName(tableName)}";
// ToDo make sure tablename can be used
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public, typeof(Entity));
// add the table attribute to the class
typeBuilder.AddTableAttribute($"\"{tableSchema}\".\"{tableName}\"");
var query = SqlHelper.LoadSql("QueryColumns.sql");
var propertyAndFieldNames = new HashSet<string>();
var columns = dbConnection.Query(query, new { DatabaseName = databaseName, TableSchema = tableSchema, TableName = tableName });
foreach (var column in columns)
{
var columnName = cxInfo.GetColumnName((string)column.ColumnName);
var isPrimaryKeyColumn = primaryKeyColumns.Contains((string)column.ColumnName); // always use the unmodified column name
var columnDefault = (string)column.ColumnDefault;
// always make primary key columns nullable (otherwise auto increment can't be used with an insert)
var fieldType = (Type)SqlHelper.MapDbTypeToType(column.DataType, column.UdtName, "YES".Equals(column.Nullable, StringComparison.InvariantCultureIgnoreCase), cxInfo.UseAdvancedTypes());
string text;
if (fieldType != null)
{
// ToDo make sure name can be used
var fieldBuilder = typeBuilder.DefineField(columnName, fieldType, FieldAttributes.Public);
fieldBuilder.AddColumnAttribute((string)column.ColumnName); // always use the unmodified column name
if (isPrimaryKeyColumn)
{
// check if the column is an identity column
if (!string.IsNullOrEmpty(columnDefault) && columnDefault.ToLower().StartsWith("nextval"))
{
fieldBuilder.AddIdentityAttribute();
}
fieldBuilder.AddPrimaryKeyAttribute();
}
text = $"{columnName} ({fieldType.GetTypeName()})";
propertyAndFieldNames.Add(columnName);
}
else
{
// field type is not mapped
text = $"{columnName} (unsupported - {column.DataType})";
}
var explorerItem = new ExplorerItem(text, ExplorerItemKind.Property, ExplorerIcon.Column)
{
SqlTypeDeclaration = column.DataType,
DragText = columnName
};
tableExplorerItem.Children.Add(explorerItem);
}
return new TableData(tableName, tableExplorerItem, typeBuilder, propertyAndFieldNames);
}
private void BuildAssociations(IDbConnection connection, string tableSchema, ICollection<TableData> preparedTables)
{
var query = SqlHelper.LoadSql("QueryForeignKeys.sql");
var foreignKeys = connection.Query(query, new { TableSchema = tableSchema });
foreach (var foreignKey in foreignKeys)
{
var constraintName = (string)foreignKey.ConstraintName;
var table = preparedTables.FirstOrDefault(t => t.Name == foreignKey.TableName);
var foreignTable = preparedTables.FirstOrDefault(t => t.Name == foreignKey.ForeignTableName);
var columnNames = (string)foreignKey.ColumnNames;
var foreignColumnNames = (string)foreignKey.ForeignColumnNames;
if (table == null || foreignTable == null || string.IsNullOrWhiteSpace(columnNames) || string.IsNullOrWhiteSpace(foreignColumnNames))
continue;
// create one-to-many association
table.CreateOneToManyAssociation(foreignTable, columnNames, foreignColumnNames, constraintName);
}
}
}
}