236 lines
10 KiB
C#
236 lines
10 KiB
C#
#region MIT license
|
|
//
|
|
// MIT license
|
|
//
|
|
// Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
#endregion
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using DbLinq.Schema;
|
|
using DbLinq.Schema.Dbml;
|
|
using DbLinq.Util;
|
|
using DbLinq.Vendor.Implementation;
|
|
|
|
namespace DbLinq.PostgreSql
|
|
{
|
|
partial class PgsqlSchemaLoader : SchemaLoader
|
|
{
|
|
private readonly Vendor.IVendor vendor = new PgsqlVendor();
|
|
public override Vendor.IVendor Vendor { get { return vendor; } set { } }
|
|
|
|
protected override void LoadStoredProcedures(Database schema, SchemaName schemaName, IDbConnection conn, NameFormat nameFormat)
|
|
{
|
|
var procs = ReadProcedures(conn, schemaName.DbName);
|
|
|
|
//4a. determine unknown types
|
|
Dictionary<long, string> typeOidToName = new Dictionary<long, string>();
|
|
|
|
foreach (DataStoredProcedure proc in procs)
|
|
{
|
|
if (proc.proallargtypes == null && !string.IsNullOrEmpty(proc.proargtypes))
|
|
proc.proallargtypes = "{" + proc.proargtypes.Replace(' ', ',') + "}"; //work around pgsql weirdness?
|
|
}
|
|
|
|
foreach (DataStoredProcedure proc in procs)
|
|
{
|
|
typeOidToName[proc.prorettype] = proc.formatted_prorettype;
|
|
if (proc.proallargtypes == null)
|
|
continue; //no args, no Oids to resolve, skip
|
|
|
|
string[] argTypes1 = parseCsvString(proc.proallargtypes); //eg. {23,24,1043}
|
|
var argTypes2 = from t in argTypes1 select long.Parse(t);
|
|
|
|
foreach (long argType in argTypes2)
|
|
{
|
|
if (!typeOidToName.ContainsKey(argType))
|
|
typeOidToName[argType] = null;
|
|
}
|
|
}
|
|
|
|
//4b. get names for unknown types
|
|
GetTypeNames(conn, schemaName.DbName, typeOidToName);
|
|
|
|
//4c. generate dbml objects
|
|
foreach (DataStoredProcedure proc in procs)
|
|
{
|
|
DbLinq.Schema.Dbml.Function dbml_fct = ParseFunction(proc, typeOidToName, nameFormat);
|
|
if (!SkipProc(dbml_fct.Name))
|
|
schema.Functions.Add(dbml_fct);
|
|
}
|
|
}
|
|
|
|
protected override void LoadConstraints(Database schema, SchemaName schemaName, IDbConnection conn, NameFormat nameFormat, Names names)
|
|
{
|
|
//TableSorter.Sort(tables, constraints); //sort tables - parents first
|
|
|
|
var constraints = ReadConstraints(conn, schemaName.DbName);
|
|
|
|
var allKeys2 = ReadForeignConstraints(conn, schemaName.DbName);
|
|
var foreignKeys = allKeys2.Where(k => k.ConstraintType == "FOREIGN KEY").ToList();
|
|
var primaryKeys = allKeys2.Where(k => k.ConstraintType == "PRIMARY KEY").ToList();
|
|
|
|
|
|
foreach (DataConstraint keyColRow in constraints)
|
|
{
|
|
//find my table:
|
|
string constraintFullDbName = GetFullDbName(keyColRow.TableName, keyColRow.TableSchema);
|
|
DbLinq.Schema.Dbml.Table table = schema.Tables.FirstOrDefault(t => constraintFullDbName == t.Name);
|
|
if (table == null)
|
|
{
|
|
WriteErrorLine("ERROR L138: Table '" + keyColRow.TableName + "' not found for column " + keyColRow.ColumnName);
|
|
continue;
|
|
}
|
|
|
|
//todo: must understand better how PKEYs are encoded.
|
|
//In Sasha's DB, they don't end with "_pkey", you need to rely on ReadForeignConstraints().
|
|
//In Northwind, they do end with "_pkey".
|
|
bool isPrimaryKey = keyColRow.ConstraintName.EndsWith("_pkey")
|
|
|| primaryKeys.Count(k => k.ConstraintName == keyColRow.ConstraintName) > 0;
|
|
|
|
if (isPrimaryKey)
|
|
{
|
|
//A) add primary key
|
|
DbLinq.Schema.Dbml.Column primaryKeyCol = table.Type.Columns.First(c => c.Name == keyColRow.ColumnName);
|
|
primaryKeyCol.IsPrimaryKey = true;
|
|
}
|
|
else
|
|
{
|
|
DataForeignConstraint dataForeignConstraint = foreignKeys.FirstOrDefault(f => f.ConstraintName == keyColRow.ConstraintName);
|
|
|
|
if (dataForeignConstraint == null)
|
|
{
|
|
string msg = "Missing data from 'constraint_column_usage' for foreign key " + keyColRow.ConstraintName;
|
|
WriteErrorLine(msg);
|
|
//throw new ApplicationException(msg);
|
|
continue; //as per Andrus, do not throw. //putting together an Adnrus_DB test case.
|
|
}
|
|
|
|
LoadForeignKey(schema, table, keyColRow.ColumnName, keyColRow.TableName, keyColRow.TableSchema,
|
|
dataForeignConstraint.ColumnName, dataForeignConstraint.ReferencedTableName,
|
|
dataForeignConstraint.ReferencedTableSchema,
|
|
keyColRow.ConstraintName, nameFormat, names);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#region function parsing
|
|
|
|
/// <summary>
|
|
/// parse pg param modes string such as '{i,i,o}'
|
|
/// </summary>
|
|
static string[] parseCsvString(string csvString)
|
|
{
|
|
if (csvString == null || (!csvString.StartsWith("{")) || (!csvString.EndsWith("}")))
|
|
return null;
|
|
string middle = csvString.Substring(1, csvString.Length - 2);
|
|
string[] parts = middle.Split(',');
|
|
return parts;
|
|
}
|
|
|
|
Function ParseFunction(DataStoredProcedure pg_proc, Dictionary<long, string> typeOidToName, NameFormat nameFormat)
|
|
{
|
|
var procedureName = CreateProcedureName(pg_proc.proname, null, nameFormat);
|
|
|
|
DbLinq.Schema.Dbml.Function dbml_func = new Function();
|
|
dbml_func.Name = procedureName.DbName;
|
|
dbml_func.Method = procedureName.MethodName;
|
|
|
|
if (pg_proc.formatted_prorettype != null && string.Compare(pg_proc.formatted_prorettype, "void") != 0)
|
|
{
|
|
var dbml_param = new Return();
|
|
dbml_param.DbType = pg_proc.formatted_prorettype;
|
|
dbml_param.Type = MapDbType(null, new DataType { SqlType = pg_proc.formatted_prorettype }).ToString();
|
|
dbml_func.Return = dbml_param;
|
|
dbml_func.IsComposable = true;
|
|
}
|
|
|
|
if (pg_proc.proallargtypes != null)
|
|
{
|
|
string[] argModes = parseCsvString(pg_proc.proargmodes);
|
|
string[] argNames = parseCsvString(pg_proc.proargnames);
|
|
string[] argTypes1 = parseCsvString(pg_proc.proallargtypes); //eg. {23,24,1043}
|
|
List<long> argTypes2 = (from t in argTypes1 select long.Parse(t)).ToList();
|
|
|
|
if (argNames == null)
|
|
{
|
|
//proc was specified as 'FUNCTION doverlaps(IN date)' - names not specified
|
|
argNames = new string[argTypes1.Length];
|
|
for (int i = 0; i < argNames.Length; i++) { argNames[i] = ((char)('a' + i)).ToString(); }
|
|
}
|
|
|
|
bool doLengthsMatch = (argTypes2.Count != argNames.Length
|
|
|| (argModes != null && argModes.Length != argNames.Length));
|
|
if (doLengthsMatch)
|
|
{
|
|
WriteErrorLine("L238 Mistmatch between modesArr, typeArr and nameArr for func " + pg_proc.proname);
|
|
return null;
|
|
}
|
|
|
|
for (int i = 0; i < argNames.Length; i++)
|
|
{
|
|
DbLinq.Schema.Dbml.Parameter dbml_param = new Parameter();
|
|
long argTypeOid = argTypes2[i];
|
|
dbml_param.DbType = typeOidToName[argTypeOid];
|
|
dbml_param.Name = argNames[i];
|
|
dbml_param.Type = MapDbType(argNames[i], new DataType { SqlType = dbml_param.DbType }).ToString();
|
|
string inOut = argModes == null ? "i" : argModes[i];
|
|
dbml_param.Direction = ParseInOut(inOut);
|
|
dbml_func.Parameters.Add(dbml_param);
|
|
}
|
|
}
|
|
|
|
return dbml_func;
|
|
}
|
|
|
|
static DbLinq.Schema.Dbml.ParameterDirection ParseInOut(string inOut)
|
|
{
|
|
switch (inOut)
|
|
{
|
|
case "i": return DbLinq.Schema.Dbml.ParameterDirection.In;
|
|
case "o": return DbLinq.Schema.Dbml.ParameterDirection.Out;
|
|
case "b": return DbLinq.Schema.Dbml.ParameterDirection.InOut;
|
|
default: return DbLinq.Schema.Dbml.ParameterDirection.InOut;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
private bool SkipProc(string name)
|
|
{
|
|
//string[] prefixes = System.Configuration.ConfigurationManager.AppSettings["postgresqlSkipProcPrefixes"].Split(',');
|
|
string[] prefixes = { "pldbg", "gbtreekey", "gbt_", "pg_buffercache", "plpgsql_", "plpgsql_call_handler" };
|
|
|
|
foreach (string s in prefixes)
|
|
{
|
|
if (name.StartsWith(s))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
}
|