diff --git a/PgRoutiner/Builder/CodeBuilders/CodeBuilders.cs b/PgRoutiner/Builder/CodeBuilders/CodeBuilders.cs index f3ce6ff..1483b79 100644 --- a/PgRoutiner/Builder/CodeBuilders/CodeBuilders.cs +++ b/PgRoutiner/Builder/CodeBuilders/CodeBuilders.cs @@ -1,5 +1,4 @@ -using Norm; -using PgRoutiner.Builder.CodeBuilders.Models; +using PgRoutiner.Builder.CodeBuilders.Models; namespace PgRoutiner.Builder.CodeBuilders; diff --git a/PgRoutiner/Builder/CodeBuilders/CodeRoutinesBuilder.cs b/PgRoutiner/Builder/CodeBuilders/CodeRoutinesBuilder.cs index a55a67f..93ab999 100644 --- a/PgRoutiner/Builder/CodeBuilders/CodeRoutinesBuilder.cs +++ b/PgRoutiner/Builder/CodeBuilders/CodeRoutinesBuilder.cs @@ -1,4 +1,4 @@ -using Norm; +using System.Data; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.Builder.CodeBuilders; @@ -26,7 +26,8 @@ protected override IEnumerable GetCodes() { foreach (var ns in settings.CustomDirs) { - if (this.connection.WithParameters(name, ns.Key).Read("select $1 similar to $2").Single()) + //if (this.connection.WithParameters(name, ns.Key).Read("select $1 similar to $2").Single()) + if (this.connection.Read([(name, DbType.AnsiString, null), (ns.Key, DbType.AnsiString, null)],"select $1 similar to $2", r => r.Val(0)).Single()) { extraNamespace = ns.Value.PathToNamespace().Replace("..", "."); RoutinesCustomDirs.Add(name, ns.Value); diff --git a/PgRoutiner/Builder/Crud.cs b/PgRoutiner/Builder/Crud.cs index 5f6614f..268c296 100644 --- a/PgRoutiner/Builder/Crud.cs +++ b/PgRoutiner/Builder/Crud.cs @@ -1,11 +1,5 @@ using System; using System.Data; -using System.Linq; -using System.Reflection.Metadata; -using System.Security.Claims; -using System.Xml.Linq; -using Norm; -using PgRoutiner.Builder.CodeBuilders; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.Builder; diff --git a/PgRoutiner/Builder/Dump/DumpBuilder.cs b/PgRoutiner/Builder/Dump/DumpBuilder.cs index 523f249..998109f 100644 --- a/PgRoutiner/Builder/Dump/DumpBuilder.cs +++ b/PgRoutiner/Builder/Dump/DumpBuilder.cs @@ -1,5 +1,5 @@ -using System.Xml.Linq; -using Norm; +using System.Data; +using System.Xml.Linq; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.Builder.Dump; @@ -173,7 +173,8 @@ static string ParseSchema(string dir, string schema) { foreach (var ns in Current.Value.CustomDirs) { - if (builder.Connection.WithParameters(objectName, ns.Key).Read("select $1 similar to $2").Single()) + //if (builder.Connection.WithParameters(objectName, ns.Key).Read("select $1 similar to $2").Single()) + if (builder.Connection.Read([(objectName, DbType.AnsiString, null), (ns.Key, DbType.AnsiString, null)], "select $1 similar to $2", r => r.Val(0)).Single()) { extraDir = ns.Value; DbObjectsCustomDirs.Add(objectName, ns.Value); diff --git a/PgRoutiner/Builder/Dump/PgDumpBuilder.cs b/PgRoutiner/Builder/Dump/PgDumpBuilder.cs index 3c2d168..eaa37b4 100644 --- a/PgRoutiner/Builder/Dump/PgDumpBuilder.cs +++ b/PgRoutiner/Builder/Dump/PgDumpBuilder.cs @@ -1,8 +1,5 @@ -using Norm; -using PgRoutiner.Builder.DiffBuilder; -using PgRoutiner.DataAccess.Models; +using PgRoutiner.DataAccess.Models; using PgRoutiner.DumpTransformers; -using static System.Net.Mime.MediaTypeNames; using static PgRoutiner.Builder.Dump.DumpBuilder; namespace PgRoutiner.Builder.Dump; @@ -693,7 +690,7 @@ private List GetDumpItemLines(string args, PgItem item, out List var result = GetDumpItemLines(args, item, lineAction: line => { var entry = line.FirstWordAfter("CREATE TYPE"); - if (entry != null) + if (entry != null && !line.Contains('\'')) { var type = new PgItem { Type = PgType.Type }; var parts = entry.Split('.'); diff --git a/PgRoutiner/Builder/Executor.cs b/PgRoutiner/Builder/Executor.cs index 3c8bf9d..ad0770c 100644 --- a/PgRoutiner/Builder/Executor.cs +++ b/PgRoutiner/Builder/Executor.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.FileSystemGlobbing.Internal; -using Norm; namespace PgRoutiner.Builder; diff --git a/PgRoutiner/Builder/Md/MarkdownDocument.cs b/PgRoutiner/Builder/Md/MarkdownDocument.cs index d0a143f..0a72e37 100644 --- a/PgRoutiner/Builder/Md/MarkdownDocument.cs +++ b/PgRoutiner/Builder/Md/MarkdownDocument.cs @@ -1,10 +1,7 @@ using System.Data; -using System.Runtime; -using System.Security.AccessControl; -using System.Xml.Linq; -using Norm; using PgRoutiner.DataAccess.Models; using static PgRoutiner.Builder.Dump.DumpBuilder; +using PgRoutiner.DataAccess; namespace PgRoutiner.Builder.Md; @@ -279,7 +276,8 @@ private void BuildRoutines(StringBuilder content, StringBuilder header, List("select $1 similar to $2").Single()) + //if (this.connection.WithParameters(result.Name, ns.Key).Read("select $1 similar to $2").Single()) + if (this.connection.Read([(result.Name, DbType.AnsiString, null), (ns.Key, DbType.AnsiString, null)], "select $1 similar to $2", r => r.Val(0)).Single()) { customDir = ns.Value; break; @@ -494,8 +492,11 @@ void WriteStats(string schema, string table) { additionalTableComment = null; additionalColumnComments = connection - .WithParameters((schema, DbType.AnsiString), (result.Table, DbType.AnsiString)) - .Read(additionalCommentsSql) + //.WithParameters((schema, DbType.AnsiString), (result.Table, DbType.AnsiString)) + .Read<(string, string, string)>( + [(schema, DbType.AnsiString, null), (result.Table, DbType.AnsiString, null)], + additionalCommentsSql, + r => (r.Val(0), r.Val(1), r.Val(2))) .Select(tuple => { additionalTableComment ??= tuple.Item1; @@ -791,7 +792,8 @@ string GetDir() { foreach (var ns in settings.CustomDirs) { - if (this.connection.WithParameters(name, ns.Key).Read("select $1 similar to $2").Single()) + //if (this.connection.WithParameters(name, ns.Key).Read("select $1 similar to $2").Single()) + if (this.connection.Read([(name, DbType.AnsiString, null), (ns.Key, DbType.AnsiString, null)], "select $1 similar to $2", r => r.Val(0)).Single()) { extraDir = ns.Value; break; diff --git a/PgRoutiner/Console.cs b/PgRoutiner/Console.cs index 5f8bcef..de8fbb4 100644 --- a/PgRoutiner/Console.cs +++ b/PgRoutiner/Console.cs @@ -1,5 +1,7 @@ using System; +using System.Diagnostics.Metrics; using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.Extensions.Configuration; namespace PgRoutiner; @@ -99,15 +101,18 @@ public static Current BindConsole(string[] args) if (hashes.Contains(arg.Alias)) { var prop = settings.GetType().GetProperty(arg.Original); - if (prop.GetType() == typeof(bool)) + if (prop.PropertyType == typeof(bool)) { - settings.GetType().GetProperty(arg.Original).SetValue(settings, true); + prop.SetValue(settings, true); } } } return settings; } + //[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_count")] + //private static extern ref int GetCountField(Current settings); + public static string[] ParseArgs(string[] rawArgs) { string[] args = new string[rawArgs.Length]; diff --git a/PgRoutiner/DataAccess/FilterTypes.cs b/PgRoutiner/DataAccess/FilterTypes.cs index b22a1d6..f6f98a2 100644 --- a/PgRoutiner/DataAccess/FilterTypes.cs +++ b/PgRoutiner/DataAccess/FilterTypes.cs @@ -1,44 +1,76 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; + public static partial class DataAccessConnectionExtensions { + public static IEnumerable FilterTypes(this NpgsqlConnection connection, List types, Current settings, string skipSimilar = null) { if (!types.Any()) { return Enumerable.Empty(); } - return connection - .WithParameters( - (settings.SchemaSimilarTo, DbType.AnsiString), - (settings.SchemaNotSimilarTo, DbType.AnsiString), - (skipSimilar, DbType.AnsiString)) - .Read<(string Schema, string Name)>(@$" - - select - schema, name - from - ( - {string.Join(" union all ", types.Select(t => $"select '{t.Schema}' as schema, '{t.Name}' as name"))} - ) sub - - where - ( $1 is null or (sub.schema similar to $1) ) - and ( $2 is null or sub.schema not similar to $2 ) - and ( {GetSchemaExpression("sub.schema")} ) - and ( $3 is null or (sub.name not similar to $3) ) + + return connection.Read<(string Schema, string Name)>( + [ + (settings.SchemaSimilarTo, DbType.AnsiString, null), + (settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (skipSimilar, DbType.AnsiString, null) + ], + @$" + + select + schema, name + from + ( + {string.Join(" union all ", types.Select(t => $"select '{t.Schema}' as schema, '{t.Name}' as name"))} + ) sub + + where + ( $1 is null or (sub.schema similar to $1) ) + and ( $2 is null or sub.schema not similar to $2 ) + and ( {GetSchemaExpression("sub.schema")} ) + and ( $3 is null or (sub.name not similar to $3) ) - ") - .Select(t => new PgItem - { - Schema = t.Schema, - Name = t.Name, - TypeName = "TYPE", - Type = PgType.Type - }); + ", r => (r.Val(0), r.Val(1)) + ).Select(t => new PgItem + { + Schema = t.Schema, + Name = t.Name, + TypeName = "TYPE", + Type = PgType.Type + }); + + //return connection + // .WithParameters( + // (settings.SchemaSimilarTo, DbType.AnsiString), + // (settings.SchemaNotSimilarTo, DbType.AnsiString), + // (skipSimilar, DbType.AnsiString)) + // .Read<(string Schema, string Name)>(@$" + + // select + // schema, name + // from + // ( + // {string.Join(" union all ", types.Select(t => $"select '{t.Schema}' as schema, '{t.Name}' as name"))} + // ) sub + + // where + // ( $1 is null or (sub.schema similar to $1) ) + // and ( $2 is null or sub.schema not similar to $2 ) + // and ( {GetSchemaExpression("sub.schema")} ) + // and ( $3 is null or (sub.name not similar to $3) ) + + // ") + // .Select(t => new PgItem + // { + // Schema = t.Schema, + // Name = t.Name, + // TypeName = "TYPE", + // Type = PgType.Type + // }); } } diff --git a/PgRoutiner/DataAccess/GetAllEnums.cs b/PgRoutiner/DataAccess/GetAllEnums.cs index 2fcbcdf..9475e42 100644 --- a/PgRoutiner/DataAccess/GetAllEnums.cs +++ b/PgRoutiner/DataAccess/GetAllEnums.cs @@ -1,28 +1,48 @@ -using Norm; - -namespace PgRoutiner.DataAccess; +namespace PgRoutiner.DataAccess; public static partial class DataAccessConnectionExtensions { public static IEnumerable<(string schema, string name, string[] values, string comment)> GetAllEnums(this NpgsqlConnection connection) { - return connection.Read<(string schema, string name, string[] values, string comment)>(@$" - select - ns.nspname, - t.typname as name, - array_agg(e.enumlabel order by e.enumsortorder) as values, - pgdesc.description as comment - from - pg_type t - inner join pg_enum e on t.oid = e.enumtypid - inner join pg_namespace ns on t.typnamespace = ns.oid - left outer join pg_catalog.pg_description pgdesc on t.oid = pgdesc.objoid - group by - ns.nspname, - t.typname, - pgdesc.description + return connection.Read<(string schema, string name, string[] values, string comment)>( + [ + ], + @$" + select + ns.nspname, + t.typname as name, + array_agg(e.enumlabel order by e.enumsortorder) as values, + pgdesc.description as comment + from + pg_type t + inner join pg_enum e on t.oid = e.enumtypid + inner join pg_namespace ns on t.typnamespace = ns.oid + left outer join pg_catalog.pg_description pgdesc on t.oid = pgdesc.objoid + group by + ns.nspname, + t.typname, + pgdesc.description + ", + r => (r.Val(0), r.Val(1), r.Val(2), r.Val(3))); + + //return connection.Read<(string schema, string name, string[] values, string comment)>(@$" + + // select + // ns.nspname, + // t.typname as name, + // array_agg(e.enumlabel order by e.enumsortorder) as values, + // pgdesc.description as comment + // from + // pg_type t + // inner join pg_enum e on t.oid = e.enumtypid + // inner join pg_namespace ns on t.typnamespace = ns.oid + // left outer join pg_catalog.pg_description pgdesc on t.oid = pgdesc.objoid + // group by + // ns.nspname, + // t.typname, + // pgdesc.description - "); + // "); } } diff --git a/PgRoutiner/DataAccess/GetConstraintNames.cs b/PgRoutiner/DataAccess/GetConstraintNames.cs index be4b38c..a77175d 100644 --- a/PgRoutiner/DataAccess/GetConstraintNames.cs +++ b/PgRoutiner/DataAccess/GetConstraintNames.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using NpgsqlTypes; using PgRoutiner.DataAccess.Models; @@ -7,10 +6,11 @@ namespace PgRoutiner.DataAccess; public static partial class DataAccessConnectionExtensions { - public static IEnumerable GetConstraintNames(this NpgsqlConnection connection, (string Schema, string Name)[] tables, PgConstraint type) => - connection - .WithParameters( - (tables.Select(t => $"{t.Schema}.{t.Name}").ToArray(), NpgsqlDbType.Varchar | NpgsqlDbType.Array), + public static IEnumerable GetConstraintNames(this NpgsqlConnection connection, (string Schema, string Name)[] tables, PgConstraint type) + { + return connection.Read<(string Schema, string Table, string Name, string Type)>( + [ + (tables.Select(t => $"{t.Schema}.{t.Name}").ToList(), null, NpgsqlDbType.Varchar | NpgsqlDbType.Array), (type switch { PgConstraint.ForeignKey => "FOREIGN KEY", @@ -18,30 +18,69 @@ public static IEnumerable GetConstraintNames(this NpgsqlConnecti PgConstraint.Check => "CHECK", PgConstraint.Unique => "UNIQUE", _ => throw new NotImplementedException() - }, NpgsqlDbType.Varchar)) - .Read<(string Schema, string Table, string Name, string Type)>(@" + }, null, NpgsqlDbType.Varchar) + ], + @$" - select - table_schema, table_name, constraint_name, constraint_type - from - information_schema.table_constraints - where - table_schema || '.' || table_name = any($1) - and constraint_type = $2 + select + table_schema, table_name, constraint_name, constraint_type + from + information_schema.table_constraints + where + table_schema || '.' || table_name = any($1) + and constraint_type = $2 - ") - .Select(t => new ConstraintName + ", r => (r.Val(0), r.Val(1), r.Val(2), r.Val(3)) + ).Select(t => new ConstraintName + { + Name = t.Name, + Schema = t.Schema, + Table = t.Table, + Type = t.Type switch { - Name = t.Name, - Schema = t.Schema, - Table = t.Table, - Type = t.Type switch - { - "FOREIGN KEY" => PgConstraint.ForeignKey, - "PRIMARY KEY" => PgConstraint.PrimaryKey, - "CHECK" => PgConstraint.Check, - "UNIQUE" => PgConstraint.Unique, - _ => throw new System.NotImplementedException() - } - }); + "FOREIGN KEY" => PgConstraint.ForeignKey, + "PRIMARY KEY" => PgConstraint.PrimaryKey, + "CHECK" => PgConstraint.Check, + "UNIQUE" => PgConstraint.Unique, + _ => throw new System.NotImplementedException() + } + }); + + //return connection + //.WithParameters( + // (tables.Select(t => $"{t.Schema}.{t.Name}").ToList(), NpgsqlDbType.Varchar | NpgsqlDbType.Array), + // (type switch + // { + // PgConstraint.ForeignKey => "FOREIGN KEY", + // PgConstraint.PrimaryKey => "PRIMARY KEY", + // PgConstraint.Check => "CHECK", + // PgConstraint.Unique => "UNIQUE", + // _ => throw new NotImplementedException() + // }, NpgsqlDbType.Varchar)) + //.Read<(string Schema, string Table, string Name, string Type)>(@" + + // select + // table_schema, table_name, constraint_name, constraint_type + // from + // information_schema.table_constraints + // where + // table_schema || '.' || table_name = any($1) + // and constraint_type = $2 + + // ") + // .Select(t => new ConstraintName + // { + // Name = t.Name, + // Schema = t.Schema, + // Table = t.Table, + // Type = t.Type switch + // { + // "FOREIGN KEY" => PgConstraint.ForeignKey, + // "PRIMARY KEY" => PgConstraint.PrimaryKey, + // "CHECK" => PgConstraint.Check, + // "UNIQUE" => PgConstraint.Unique, + // _ => throw new System.NotImplementedException() + // } + // }); + } } diff --git a/PgRoutiner/DataAccess/GetDomains.cs b/PgRoutiner/DataAccess/GetDomains.cs index f9cdda8..170ad0b 100644 --- a/PgRoutiner/DataAccess/GetDomains.cs +++ b/PgRoutiner/DataAccess/GetDomains.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -8,33 +7,62 @@ public static partial class DataAccessConnectionExtensions { public static IEnumerable GetDomains(this NpgsqlConnection connection, Current settings, string skipSimilar = null) { - return connection - .WithParameters( - (settings.SchemaSimilarTo, DbType.AnsiString), - (settings.SchemaNotSimilarTo, DbType.AnsiString), - (skipSimilar, DbType.AnsiString)) - .Read<(string Schema, string Name)>(@$" + return connection.Read<(string Schema, string Name)>( + [ + (settings.SchemaSimilarTo, DbType.AnsiString, null), + (settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (skipSimilar, DbType.AnsiString, null) + ], + @$" + select + distinct + d.domain_schema, + d.domain_name + from + information_schema.domains d + where + ( $1 is null or (d.domain_schema similar to $1) ) + and ( $2 is null or d.domain_schema not similar to $2 ) + and ( {GetSchemaExpression("d.domain_schema")} ) - select - distinct - d.domain_schema, - d.domain_name - from - information_schema.domains d - where - ( $1 is null or (d.domain_schema similar to $1) ) - and ( $2 is null or d.domain_schema not similar to $2 ) - and ( {GetSchemaExpression("d.domain_schema")} ) + and ( $3 is null or (d.domain_name not similar to $3) ) + ", r => (r.Val(0), r.Val(1)) + ) + .Select(t => new PgItem + { + Schema = t.Schema, + Name = t.Name, + TypeName = "DOMAIN", + Type = PgType.Domain + }); + + //return connection + // .WithParameters( + // (settings.SchemaSimilarTo, DbType.AnsiString), + // (settings.SchemaNotSimilarTo, DbType.AnsiString), + // (skipSimilar, DbType.AnsiString)) + // .Read<(string Schema, string Name)>(@$" - and ( $3 is null or (d.domain_name not similar to $3) ) + // select + // distinct + // d.domain_schema, + // d.domain_name + // from + // information_schema.domains d + // where + // ( $1 is null or (d.domain_schema similar to $1) ) + // and ( $2 is null or d.domain_schema not similar to $2 ) + // and ( {GetSchemaExpression("d.domain_schema")} ) - ") - .Select(t => new PgItem - { - Schema = t.Schema, - Name = t.Name, - TypeName = "DOMAIN", - Type = PgType.Domain - }); + // and ( $3 is null or (d.domain_name not similar to $3) ) + + // ") + // .Select(t => new PgItem + // { + // Schema = t.Schema, + // Name = t.Name, + // TypeName = "DOMAIN", + // Type = PgType.Domain + // }); } } diff --git a/PgRoutiner/DataAccess/GetEnumComments.cs b/PgRoutiner/DataAccess/GetEnumComments.cs index 45d4f25..9878c71 100644 --- a/PgRoutiner/DataAccess/GetEnumComments.cs +++ b/PgRoutiner/DataAccess/GetEnumComments.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -8,30 +7,61 @@ public static partial class DataAccessConnectionExtensions { public static IEnumerable GetEnumComments(this NpgsqlConnection connection, Current settings, string schema) { - return connection - .WithParameters( - (schema, DbType.AnsiString), - (settings.MdNotSimilarTo, DbType.AnsiString), - (settings.MdSimilarTo, DbType.AnsiString)) - .Read(@$" + return connection.Read( + [ + (schema, DbType.AnsiString, null), + (settings.MdNotSimilarTo, DbType.AnsiString, null), + (settings.MdSimilarTo, DbType.AnsiString, null) + ], + @$" + select + t.typname as name, + pgdesc.description as comment, + string_agg('''' || e.enumlabel || '''', ', ' order by e.enumsortorder) as values + from + pg_type t + inner join pg_enum e on t.oid = e.enumtypid + inner join pg_namespace ns on t.typnamespace = ns.oid + left outer join pg_catalog.pg_description pgdesc on t.oid = pgdesc.objoid + where + ns.nspname = $1 + and ($2 is null or t.typname not similar to $2) + and ($3 is null or t.typname similar to $3) + group by + t.typname, + pgdesc.description; + ", r => new EnumComment + { + Name = r.Val(0), + Comment = r.Val(1), + Values = r.Val(2) + }); - select - t.typname as name, - pgdesc.description as comment, - string_agg('''' || e.enumlabel || '''', ', ' order by e.enumsortorder) as values - from - pg_type t - inner join pg_enum e on t.oid = e.enumtypid - inner join pg_namespace ns on t.typnamespace = ns.oid - left outer join pg_catalog.pg_description pgdesc on t.oid = pgdesc.objoid - where - ns.nspname = $1 - and ($2 is null or t.typname not similar to $2) - and ($3 is null or t.typname similar to $3) - group by - t.typname, - pgdesc.description; - "); + //return connection + // .WithParameters( + // (schema, DbType.AnsiString), + // (settings.MdNotSimilarTo, DbType.AnsiString), + // (settings.MdSimilarTo, DbType.AnsiString)) + // .Read(@$" + + // select + // t.typname as name, + // pgdesc.description as comment, + // string_agg('''' || e.enumlabel || '''', ', ' order by e.enumsortorder) as values + // from + // pg_type t + // inner join pg_enum e on t.oid = e.enumtypid + // inner join pg_namespace ns on t.typnamespace = ns.oid + // left outer join pg_catalog.pg_description pgdesc on t.oid = pgdesc.objoid + // where + // ns.nspname = $1 + // and ($2 is null or t.typname not similar to $2) + // and ($3 is null or t.typname similar to $3) + // group by + // t.typname, + // pgdesc.description; + + // "); } } diff --git a/PgRoutiner/DataAccess/GetEnumValueAggregate.cs b/PgRoutiner/DataAccess/GetEnumValueAggregate.cs index e8d979a..4c856dc 100644 --- a/PgRoutiner/DataAccess/GetEnumValueAggregate.cs +++ b/PgRoutiner/DataAccess/GetEnumValueAggregate.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; namespace PgRoutiner.DataAccess; @@ -7,21 +6,38 @@ public static partial class DataAccessConnectionExtensions { public static string GetEnumValueAggregate(this NpgsqlConnection connection, string schema, string name) { - return connection - .WithParameters( - (schema, DbType.AnsiString), - (name, DbType.AnsiString)) - .Read(@$" + return connection.Read( + [ + (schema, DbType.AnsiString, null), + (name, DbType.AnsiString, null) + ], +@$" + select + string_agg('''' || e.enumlabel || '''', ', ' order by e.enumsortorder) + from + pg_type t + inner join pg_enum e on t.oid = e.enumtypid + inner join pg_namespace ns on t.typnamespace = ns.oid + where + ns.nspname = $1 and t.typname = $2; + ", r => r.Val(0)) + .FirstOrDefault(); - select - string_agg('''' || e.enumlabel || '''', ', ' order by e.enumsortorder) - from - pg_type t - inner join pg_enum e on t.oid = e.enumtypid - inner join pg_namespace ns on t.typnamespace = ns.oid - where - ns.nspname = $1 and t.typname = $2; + //return connection + // .WithParameters( + // (schema, DbType.AnsiString), + // (name, DbType.AnsiString)) + // .Read(@$" - ").FirstOrDefault(); + // select + // string_agg('''' || e.enumlabel || '''', ', ' order by e.enumsortorder) + // from + // pg_type t + // inner join pg_enum e on t.oid = e.enumtypid + // inner join pg_namespace ns on t.typnamespace = ns.oid + // where + // ns.nspname = $1 and t.typname = $2; + + // ").FirstOrDefault(); } } diff --git a/PgRoutiner/DataAccess/GetExtensions.cs b/PgRoutiner/DataAccess/GetExtensions.cs index 6deebf2..3fbc326 100644 --- a/PgRoutiner/DataAccess/GetExtensions.cs +++ b/PgRoutiner/DataAccess/GetExtensions.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -9,7 +8,7 @@ public static partial class DataAccessConnectionExtensions public static IEnumerable GetExtensions(this NpgsqlConnection connection) { return connection - .Read("select extname from pg_extension") + .Read([],"select extname from pg_extension", r => r.Val(0)) .Select(s => new PgItem { Schema = null, @@ -17,5 +16,15 @@ public static IEnumerable GetExtensions(this NpgsqlConnection connection TypeName = "EXTENSION", Type = PgType.Extension }); + + //return connection + // .Read("select extname from pg_extension") + // .Select(s => new PgItem + // { + // Schema = null, + // Name = s, + // TypeName = "EXTENSION", + // Type = PgType.Extension + // }); } } diff --git a/PgRoutiner/DataAccess/GetPartitionTables.cs b/PgRoutiner/DataAccess/GetPartitionTables.cs index 698d718..efe6095 100644 --- a/PgRoutiner/DataAccess/GetPartitionTables.cs +++ b/PgRoutiner/DataAccess/GetPartitionTables.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -10,10 +9,11 @@ public static partial class DataAccessConnectionExtensions this NpgsqlConnection connection, PgItem table) { return connection - .WithParameters( - (table.Schema, DbType.AnsiString), - (table.Name, DbType.AnsiString)) - .Read<(string Schema, string Table, string Expression)>(@$" + .Read<(string Schema, string Table, string Expression)>( + [ + (table.Schema, DbType.AnsiString, null), + (table.Name, DbType.AnsiString, null) + ], @$" select nmsp_child.nspname, child.relname, @@ -27,13 +27,36 @@ public static partial class DataAccessConnectionExtensions where nmsp_parent.nspname = $1 and parent.relname = $2; - "); + ", r => (r.Val(0), r.Val(1), r.Val(2))); + + //return connection + // .WithParameters( + // (table.Schema, DbType.AnsiString), + // (table.Name, DbType.AnsiString)) + // .Read<(string Schema, string Table, string Expression)>(@$" + // select + // nmsp_child.nspname, + // child.relname, + // pg_get_expr(child.relpartbound, child.oid, true) + // from + // pg_inherits + // inner join pg_class parent on pg_inherits.inhparent = parent.oid + // inner join pg_class child on pg_inherits.inhrelid = child.oid + // inner join pg_namespace nmsp_parent on nmsp_parent.oid = parent.relnamespace + // inner join pg_namespace nmsp_child on nmsp_child.oid = child.relnamespace + // where + // nmsp_parent.nspname = $1 and parent.relname = $2; + + //"); } public static IEnumerable<(string Schema, string Table)> GetAllPartitionTables(this NpgsqlConnection connection) { - return connection.Read<(string Schema, string Table)>(@$" + return connection + .Read<(string Schema, string Table)>( + [ + ], @$" select nmsp_child.nspname, child.relname @@ -46,6 +69,23 @@ public static partial class DataAccessConnectionExtensions where pg_get_expr(child.relpartbound, child.oid, true) is not null; - "); + + ", r => (r.Val(0), r.Val(1))); + + //return connection.Read<(string Schema, string Table)>(@$" + + // select + // nmsp_child.nspname, + // child.relname + // from + // pg_inherits + // inner join pg_class parent on pg_inherits.inhparent = parent.oid + // inner join pg_class child on pg_inherits.inhrelid = child.oid + // inner join pg_namespace nmsp_parent on nmsp_parent.oid = parent.relnamespace + // inner join pg_namespace nmsp_child on nmsp_child.oid = child.relnamespace + // where + // pg_get_expr(child.relpartbound, child.oid, true) is not null; + + //"); } } diff --git a/PgRoutiner/DataAccess/GetPgItems.cs b/PgRoutiner/DataAccess/GetPgItems.cs index b8ece91..f8286d9 100644 --- a/PgRoutiner/DataAccess/GetPgItems.cs +++ b/PgRoutiner/DataAccess/GetPgItems.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using NpgsqlTypes; using PgRoutiner.DataAccess.Models; @@ -26,11 +25,132 @@ public static IEnumerable GetPgItems( name = split[1]; } + return connection + .Read<(string Schema, string Name, string Type)>( + [ + (schema == "*" ? null : schema, DbType.AnsiString, null), + (name == "*" ? null : name, DbType.AnsiString, null), + (settings.RoutinesLanguages.ToList(), null, NpgsqlDbType.Array | NpgsqlDbType.Text) + ], @$" + + -- tables + select + t.table_schema as schema, + t.table_name as name, + t.table_type as type + from + information_schema.tables t + where + ( $1 is null or t.table_schema = $1 ) and + ( $2 is null or t.table_name = $2 ) and + ( {GetSchemaExpression("t.table_schema")} ) and + not exists ( + + select + 1 + from + pg_inherits + inner join pg_class parent on pg_inherits.inhparent = parent.oid + inner join pg_class child on pg_inherits.inhrelid = child.oid + inner join pg_namespace nmsp_parent on nmsp_parent.oid = parent.relnamespace + inner join pg_namespace nmsp_child on nmsp_child.oid = child.relnamespace + where + nmsp_child.nspname = t.table_schema and child.relname = t.table_name + ) + + union all + + -- function and procedures + select + distinct + r.routine_schema as schema, + r.routine_name as name, + r.routine_type as type + from + information_schema.routines r + where + lower(r.external_language) = any($3) and + ( $1 is null or r.routine_schema = $1 ) and + ( $2 is null or r.routine_name = $2 ) and + ( {GetSchemaExpression("r.routine_schema")} ) + + union all + + -- domains + select + distinct + d.domain_schema as schema, + d.domain_name as name, + 'DOMAIN' as type + from + information_schema.domains d + where + ( $1 is null or d.domain_schema = $1 ) and + ( $2 is null or d.domain_name = $2 ) and + ( {GetSchemaExpression("d.domain_schema")} ) + + union all + + -- types + select + sub.schema as schema, + sub.name as name, + 'TYPE' as type + from + ( + {string.Join(" union all ", types.Select(t => $"select '{t.Schema}' as schema, '{t.Name}' as name"))} + ) sub + where + ( $1 is null or sub.schema = $1 ) and + ( $2 is null or sub.name = $2 ) and + ( {GetSchemaExpression("sub.schema")} ) + + union all + + select + sc.schema_name as schema, + null as name, + 'SCHEMA' as type + from + information_schema.schemata sc + where + ( ($1 is null and $2 is null) or sc.schema_name = $1 or sc.schema_name = $2 ) + + union all + + select + null as schema, + e.extname as name, + 'EXTENSION' as type + from pg_extension e + where + ( ($1 is null and $2 is null) or e.extname = $1 or e.extname = $2 ) + + ", r => (r.Val(0), r.Val(1), r.Val(2))) + .Select(t => new PgItem + { + Schema = t.Schema, + Name = t.Name, + Type = t.Type switch + { + "BASE TABLE" => PgType.Table, + "VIEW" => PgType.View, + "FUNCTION" => PgType.Function, + "DOMAIN" => PgType.Domain, + "TYPE" => PgType.Type, + "SCHEMA" => PgType.Schema, + "EXTENSION" => PgType.Extension, + _ => PgType.Unknown + }, + TypeName = t.Type.ToUpperInvariant(), + }); + + /* return connection .WithParameters( (schema == "*" ? null : schema, DbType.AnsiString), (name == "*" ? null : name, DbType.AnsiString), - (settings.RoutinesLanguages, NpgsqlDbType.Array | NpgsqlDbType.Text)) + (settings.RoutinesLanguages.ToList(), NpgsqlDbType.Array | NpgsqlDbType.Text)) .Read<(string Schema, string Name, string Type)>(@$" -- tables @@ -143,5 +263,6 @@ from pg_extension e }, TypeName = t.Type.ToUpperInvariant(), }); -} + */ + } } diff --git a/PgRoutiner/DataAccess/GetRoutineComments.cs b/PgRoutiner/DataAccess/GetRoutineComments.cs index de3dd73..8980ebd 100644 --- a/PgRoutiner/DataAccess/GetRoutineComments.cs +++ b/PgRoutiner/DataAccess/GetRoutineComments.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using NpgsqlTypes; using PgRoutiner.DataAccess.Models; @@ -7,13 +6,108 @@ namespace PgRoutiner.DataAccess; public static partial class DataAccessConnectionExtensions { - public static IEnumerable GetRoutineComments(this NpgsqlConnection connection, Current settings, string schema) => - connection + public static IEnumerable GetRoutineComments(this NpgsqlConnection connection, Current settings, string schema) + { + return connection.Read( + [ + (schema, DbType.AnsiString, null), + (settings.MdNotSimilarTo, DbType.AnsiString, null), + (settings.MdSimilarTo, DbType.AnsiString, null), + (settings.RoutinesLanguages.ToList(), null, NpgsqlDbType.Array | NpgsqlDbType.Text) + ], @" + select + + lower(r.routine_type) as type, + r.routine_name as name, + r.specific_name, + + case when r.data_type = 'USER-DEFINED' and + r.type_udt_catalog is not null and + r.type_udt_schema is not null and + r.type_udt_name is not null + then 'setof ' || r.type_udt_name + else r.data_type + end as ""returns"", + + lower(r.external_language) as language, + + pgdesc.description as comment, + proc.proretset as is_set, + + r.routine_definition as definition, + + coalesce(array_agg( + case when p.parameter_mode = 'IN' then '' else lower(p.parameter_mode) || ' ' end + || p.parameter_name || ' ' + || coalesce( + case + when p.data_type = 'ARRAY' then regexp_replace(p.udt_name, '^[_]', '') || '[]' + when p.data_type = 'USER-DEFINED' then p.udt_schema || '.' || p.udt_name + else p.data_type + end + || case when p.parameter_default is null then '' else ' DEFAULT ' || p.parameter_default end, + '') + order by p.ordinal_position + ) filter (where p.parameter_name is not null), array[]::text[]) as parameters, + + + r.routine_name || + '(' || + array_to_string( + array_agg( + coalesce( + case + when p.data_type = 'ARRAY' then regexp_replace(p.udt_name, '^[_]', '') || '[]' + when p.data_type = 'USER-DEFINED' then p.udt_schema || '.' || p.udt_name + else p.data_type + end, + '') + order by p.ordinal_position + ), + ', ' + ) || + ')' as signature + + from + information_schema.routines r + left outer join information_schema.parameters p + on r.specific_name = p.specific_name and r.specific_schema = p.specific_schema and (p.parameter_mode = 'IN' or p.parameter_mode = 'INOUT') + + inner join pg_catalog.pg_proc proc on r.specific_name = proc.proname || '_' || proc.oid + left outer join pg_catalog.pg_description pgdesc on proc.oid = pgdesc.objoid + where + r.specific_schema = $1 + and lower(r.external_language) = any($4) + + and ($2 is null or r.routine_name not similar to $2) + and ($3 is null or r.routine_name similar to $3) + + group by + r.specific_name, r.routine_type, r.external_language, r.routine_name, + r.data_type, r.type_udt_catalog, r.type_udt_schema, r.type_udt_name, + pgdesc.description, proc.proretset, r.routine_definition + ", + + r => new RoutineComment + { + Type = r.Val(0), + Name = r.Val(1), + SpecificName = r.Val(2), + Returns = r.Val(3), + Language = r.Val(4), + Comment = r.Val(5), + IsSet = r.Val(6), + Definition = r.Val(7), + Parameters = r.Val(8), + Signature = r.Val(9) + }); + /* + return connection .WithParameters( (schema, DbType.AnsiString), (settings.MdNotSimilarTo, DbType.AnsiString), (settings.MdSimilarTo, DbType.AnsiString), - (settings.RoutinesLanguages, NpgsqlDbType.Array | NpgsqlDbType.Text)) + (settings.RoutinesLanguages.ToList(), NpgsqlDbType.Array | NpgsqlDbType.Text)) .Read(@" select @@ -88,4 +182,6 @@ group by r.data_type, r.type_udt_catalog, r.type_udt_schema, r.type_udt_name, pgdesc.description, proc.proretset, r.routine_definition "); + */ + } } diff --git a/PgRoutiner/DataAccess/GetRoutineCount.cs b/PgRoutiner/DataAccess/GetRoutineCount.cs index 519d8b3..ed003f7 100644 --- a/PgRoutiner/DataAccess/GetRoutineCount.cs +++ b/PgRoutiner/DataAccess/GetRoutineCount.cs @@ -1,19 +1,40 @@ using System.Data; -using Norm; using NpgsqlTypes; namespace PgRoutiner.DataAccess; public static partial class DataAccessConnectionExtensions { - public static long GetRoutineCount(this NpgsqlConnection connection, Current settings, - string schemaSimilarTo = null, string schemaNotSimilarTo = null) => connection + public static long GetRoutineCount(this NpgsqlConnection connection, Current settings, + string schemaSimilarTo = null, string schemaNotSimilarTo = null) + { + return connection.Read([ + (schemaSimilarTo ?? settings.SchemaSimilarTo, DbType.AnsiString, null), + (schemaNotSimilarTo ?? settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (settings.RoutinesNotSimilarTo, DbType.AnsiString, null), + (settings.RoutinesSimilarTo, DbType.AnsiString, null), + (settings.RoutinesLanguages.ToList(), null, NpgsqlDbType.Array | NpgsqlDbType.Text) + ], @$" + select + count(*) + from + information_schema.routines r + where + lower(r.external_language) = any($5) + and + ( $1 is null or (r.specific_schema similar to $1) ) + and ( $2 is null or r.specific_schema not similar to $2 ) + and ( {GetSchemaExpression("r.specific_schema")} ) + and ($3 is null or r.routine_name not similar to $3) + and ($4 is null or r.routine_name similar to $4)", r => r.Val(0)).Single(); + /* + return connection .WithParameters( (schemaSimilarTo ?? settings.SchemaSimilarTo, DbType.AnsiString), (schemaNotSimilarTo ?? settings.SchemaNotSimilarTo, DbType.AnsiString), (settings.RoutinesNotSimilarTo, DbType.AnsiString), (settings.RoutinesSimilarTo, DbType.AnsiString), - (settings.RoutinesLanguages, NpgsqlDbType.Array | NpgsqlDbType.Text)) + (settings.RoutinesLanguages.ToList(), NpgsqlDbType.Array | NpgsqlDbType.Text)) .Read(@$" select @@ -31,4 +52,6 @@ information_schema.routines r and ($4 is null or r.routine_name similar to $4) ") .Single(); + */ + } } diff --git a/PgRoutiner/DataAccess/GetRoutineGroups.cs b/PgRoutiner/DataAccess/GetRoutineGroups.cs index 65c38e4..fcfae4b 100644 --- a/PgRoutiner/DataAccess/GetRoutineGroups.cs +++ b/PgRoutiner/DataAccess/GetRoutineGroups.cs @@ -1,17 +1,190 @@ using System.Data; -using Norm; -using Newtonsoft.Json; -using PgRoutiner.DataAccess.Models; +using System.Text.Json; +using System.Text.Json.Serialization; using NpgsqlTypes; +using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; +[JsonSerializable(typeof(PgParameter[]))] +internal partial class PgParameterSerializerContext : JsonSerializerContext; + public static partial class DataAccessConnectionExtensions { + private static PgParameterSerializerContext jsonCtx = new(new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + public static IEnumerable> GetRoutineGroups( this NpgsqlConnection connection, Current settings, bool all = true, string skipSimilar = null, string schemaSimilarTo = null, string schemaNotSimilarTo = null) { + return connection.Read<( + uint Oid, + string SpecificSchema, + string SpecificName, + string RoutineName, + string RoutineDefinition, + string FullRoutineDefinition, + string Description, + string Language, + string RoutineType, + string TypeUdtName, + bool IsSet, + string DataType, + string Parameters)>([ + (schemaSimilarTo ?? settings.SchemaSimilarTo, DbType.AnsiString, null), + (schemaNotSimilarTo ?? settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (settings.RoutinesNotSimilarTo, DbType.AnsiString, null), + (settings.RoutinesSimilarTo, DbType.AnsiString, null), + (all, DbType.Boolean, null), + (skipSimilar, DbType.AnsiString, null), + (settings.RoutinesLanguages.ToList(), null, NpgsqlDbType.Array | NpgsqlDbType.Text) + ], @$" + + select + proc.oid, + r.specific_schema, + r.specific_name, + r.routine_name, + + r.routine_definition, + pg_get_functiondef(proc.oid) as full_routine_definition, + + pgdesc.description, + lower(r.external_language) as language, + lower(r.routine_type) as routine_type, + + regexp_replace(r.type_udt_name, '^[_]', '') as type_udt_name, + proc.proretset, + + case + when array_length((array_agg(p.ordinal_position) filter (where p.ordinal_position is not null and p.parameter_mode = 'OUT')), 1) > 0 + then 'record' + else r.data_type + end as data_type, + + coalesce ( + json_agg( + json_build_object( + 'ordinal', p.ordinal_position, + 'name', p.parameter_name, + 'type', regexp_replace(p.udt_name, '^[_]', ''), + 'dataType', p.data_type, + 'isArray', p.data_type = 'ARRAY', + 'default', p.parameter_default, + 'dataTypeFormatted', case when p.data_type = 'USER-DEFINED' + then p.udt_name + else case when p.udt_schema <> 'pg_catalog' + then p.udt_schema || '.' + else '' + end || p.udt_name::regtype + end || + case when p.data_type <> 'integer' and p.data_type <> 'bigint' and p.data_type <> 'smallint' + then + case when p.character_maximum_length is not null + then '(' || cast(p.character_maximum_length as varchar) || ')' + else + case when p.numeric_precision is not null + then '(' || cast(p.numeric_precision as varchar) || ',' || cast(p.numeric_scale as varchar) || ')' + else '' + end + end + else '' + end + ) + order by + p.ordinal_position + ) + filter ( + where + p.ordinal_position is not null + and (p.parameter_mode = 'IN' or p.parameter_mode = 'INOUT') + ), + '[]' + ) as parameters + + from + information_schema.routines r + inner join pg_catalog.pg_proc proc on r.specific_name = proc.proname || '_' || proc.oid + left outer join pg_catalog.pg_description pgdesc on proc.oid = pgdesc.objoid + + left outer join information_schema.parameters p + on r.specific_name = p.specific_name and r.specific_schema = p.specific_schema + + where + lower(r.external_language) = any($7) + and + ( $1 is null or (r.specific_schema similar to $1) ) + and ( $2 is null or r.specific_schema not similar to $2 ) + and ( {GetSchemaExpression("r.specific_schema")} ) + + and ($3 is null or r.routine_name not similar to $3) + and ($4 is null or r.routine_name similar to $4) + + and ( + ($5 is true) + or (r.type_udt_name is null and r.routine_type = 'PROCEDURE') + or (r.type_udt_name is not null and r.type_udt_name <> 'trigger' and r.type_udt_name <> 'refcursor') + ) + + and ( $6 is null or (r.routine_name not similar to $6) ) + group by + proc.oid, + r.specific_schema, + r.specific_name, + r.routine_name, + r.routine_definition, + pgdesc.description, + r.external_language, + r.routine_type, + r.type_udt_name, + proc.proretset, + r.data_type + order by + r.specific_schema, + r.routine_name + ", + r => ( + r.Val(0), + r.Val(1), + r.Val(2), + r.Val(3), + r.Val(4), + r.Val(5), + r.Val(6), + r.Val(7), + r.Val(8), + r.Val(9), + r.Val(10), + r.Val(11), + r.Val(12) + )) + .Select(t => + { + return new PgRoutineGroup + { + Oid = t.Oid, + SpecificSchema = t.SpecificSchema, + SpecificName = t.SpecificName, + RoutineName = t.RoutineName, + Definition = t.RoutineDefinition, + FullDefinition = t.FullRoutineDefinition, + Description = t.Description, + Language = t.Language, + RoutineType = t.RoutineType, + TypeUdtName = t.TypeUdtName, + IsSet = t.IsSet, + DataType = t.DataType, + Parameters = JsonSerializer.Deserialize(t.Parameters, jsonCtx.PgParameterArray).ToList() + + }; + }) + .GroupBy(i => (i.SpecificSchema, i.RoutineName)); + + + /* return connection .WithParameters( (schemaSimilarTo ?? settings.SchemaSimilarTo, DbType.AnsiString), @@ -20,7 +193,7 @@ public static partial class DataAccessConnectionExtensions (settings.RoutinesSimilarTo, DbType.AnsiString), (all, DbType.Boolean), (skipSimilar, DbType.AnsiString), - (settings.RoutinesLanguages, NpgsqlDbType.Array | NpgsqlDbType.Text)) + (settings.RoutinesLanguages.ToList(), NpgsqlDbType.Array | NpgsqlDbType.Text)) .Read<( uint Oid, string SpecificSchema, @@ -156,5 +329,6 @@ order by Parameters = JsonConvert.DeserializeObject>(t.Parameters) }) .GroupBy(i => (i.SpecificSchema, i.RoutineName)); + */ } } diff --git a/PgRoutiner/DataAccess/GetRoutineReturnsRecord.cs b/PgRoutiner/DataAccess/GetRoutineReturnsRecord.cs index 1f0c9f6..1697df5 100644 --- a/PgRoutiner/DataAccess/GetRoutineReturnsRecord.cs +++ b/PgRoutiner/DataAccess/GetRoutineReturnsRecord.cs @@ -1,13 +1,67 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; public static partial class DataAccessConnectionExtensions { - public static IEnumerable GetRoutineReturnsRecord(this NpgsqlConnection connection, PgRoutineGroup routine) => - connection + public static IEnumerable GetRoutineReturnsRecord(this NpgsqlConnection connection, PgRoutineGroup routine) + { + return connection.Read( + [ + (routine.SpecificName, DbType.AnsiString, null), + (routine.SpecificSchema, DbType.AnsiString, null) + ], @" + + select + p.ordinal_position as ordinal, + p.parameter_name as name, + regexp_replace(p.udt_name, '^[_]', '') as type, + p.data_type, + p.data_type = 'ARRAY' as array, + true as nullable, + + case when p.data_type = 'USER-DEFINED' + then p.udt_name + else case when p.udt_schema <> 'pg_catalog' + then p.udt_schema || '.' + else '' + end || p.udt_name::regtype + end || + case when p.data_type <> 'integer' and p.data_type <> 'bigint' and p.data_type <> 'smallint' + then + case when p.character_maximum_length is not null + then '(' || cast(p.character_maximum_length as varchar) || ')' + else + case when p.numeric_precision is not null + then '(' || cast(p.numeric_precision as varchar) || ',' || cast(p.numeric_scale as varchar) || ')' + else '' + end + end + else '' + end as data_type_formatted + + from + information_schema.parameters p + where + p.ordinal_position is not null + and (p.parameter_mode = 'OUT' or p.parameter_mode = 'INOUT') + and p.specific_name = $1 and p.specific_schema = $2 + order by + p.ordinal_position + + ", r => new PgReturns + { + Ordinal = r.Val("ordinal"), + Name = r.Val("name"), + Type = r.Val("type"), + DataType = r.Val("data_type"), + Array = r.Val("array"), + Nullable = r.Val("nullable"), + DataTypeFormatted = r.Val("data_type_formatted") + }); + /* + return connection .WithParameters( (routine.SpecificName, DbType.AnsiString), (routine.SpecificSchema, DbType.AnsiString)) @@ -51,4 +105,6 @@ order by p.ordinal_position "); + */ + } } diff --git a/PgRoutiner/DataAccess/GetRoutineReturnsTable.cs b/PgRoutiner/DataAccess/GetRoutineReturnsTable.cs index 9097d99..1fa32be 100644 --- a/PgRoutiner/DataAccess/GetRoutineReturnsTable.cs +++ b/PgRoutiner/DataAccess/GetRoutineReturnsTable.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -16,8 +15,35 @@ public static IEnumerable GetRoutineReturnsTable(this NpgsqlConnectio return connection.GetTypeColumnsForRoutine(routine); } - private static IEnumerable GetTableColumnsForRoutine(this NpgsqlConnection connection, PgRoutineGroup routine) => - connection + private static IEnumerable GetTableColumnsForRoutine(this NpgsqlConnection connection, PgRoutineGroup routine) + { + return connection.Read([(routine.TypeUdtName, DbType.AnsiString, null)], @" + select + c.ordinal_position as ordinal, + c.column_name as name, + regexp_replace(c.udt_name, '^[_]', '') as type, + c.data_type, + c.data_type = 'ARRAY' as array, + c.is_nullable = 'YES' as nullable + + from + information_schema.columns c + where + c.table_name = $1 + order by + c.ordinal_position + + ", r => new PgReturns + { + Ordinal = r.Val("ordinal"), + Name = r.Val("name"), + Type = r.Val("type"), + DataType = r.Val("data_type"), + Array = r.Val("array"), + Nullable = r.Val("nullable") + }); + /* + return connection .WithParameters((routine.TypeUdtName, DbType.AnsiString)) .Read(@" @@ -37,9 +63,39 @@ order by c.ordinal_position "); + */ + } - private static IEnumerable GetTypeColumnsForRoutine(this NpgsqlConnection connection, PgRoutineGroup routine) => - connection + private static IEnumerable GetTypeColumnsForRoutine(this NpgsqlConnection connection, PgRoutineGroup routine) + { + return connection.Read([(routine.TypeUdtName, DbType.AnsiString, null)], @" + select + (row_number() over ())::int as ordinal, + a.attname as name, + regexp_replace(t.typname, '^[_]', '') as type, + null as data_type, + t.typinput::text like 'array_%' as array, + not t.typnotnull as nullable + + from pg_class c + inner join pg_attribute a on c.oid = a.attrelid + inner join pg_type t on a.atttypid = t.oid + where + c.relname = $1 + + ", r => new PgReturns + { + Ordinal = r.Val("ordinal"), + Name = r.Val("name"), + Type = r.Val("type"), + DataType = r.Val("data_type"), + Array = r.Val("array"), + Nullable = r.Val("nullable") + }); + + + /* + return connection .WithParameters((routine.TypeUdtName, DbType.AnsiString)) .Read(@" @@ -58,4 +114,6 @@ from pg_class c c.relname = $1 "); -} + */ + } +} \ No newline at end of file diff --git a/PgRoutiner/DataAccess/GetRoutines.cs b/PgRoutiner/DataAccess/GetRoutines.cs index 4f24a0e..44300f3 100644 --- a/PgRoutiner/DataAccess/GetRoutines.cs +++ b/PgRoutiner/DataAccess/GetRoutines.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using NpgsqlTypes; using PgRoutiner.DataAccess.Models; @@ -9,11 +8,46 @@ public static partial class DataAccessConnectionExtensions { public static IEnumerable GetRoutines(this NpgsqlConnection connection, Current settings) { + return connection.Read<(string Schema, string Name, string Type)>( + [ + (settings.SchemaSimilarTo, DbType.AnsiString, null), + (settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (settings.RoutinesLanguages.ToList(), null, NpgsqlDbType.Array | NpgsqlDbType.Text) + ],@$" + select + distinct + r.routine_schema, + r.routine_name, + r.routine_type + from + information_schema.routines r + where + lower(r.external_language) = any($3) + and + ( $1 is null or (r.specific_schema similar to $1) ) + and ( $2 is null or r.specific_schema not similar to $2 ) + and ( {GetSchemaExpression("r.specific_schema")} ) + + ", r => (r.Val(0), r.Val(1), r.Val(2)) + ) + .Select(t => new PgItem + { + Schema = t.Schema, + Name = t.Name, + TypeName = t.Type, + Type = t.Type switch + { + "FUNCTION" => PgType.Function, + "PROCEDURE" => PgType.Procedure, + _ => PgType.Unknown + } + }); + /* return connection .WithParameters( (settings.SchemaSimilarTo, DbType.AnsiString), (settings.SchemaNotSimilarTo, DbType.AnsiString), - (settings.RoutinesLanguages, NpgsqlDbType.Array | NpgsqlDbType.Text)) + (settings.RoutinesLanguages.ToList(), NpgsqlDbType.Array | NpgsqlDbType.Text)) .Read<(string Schema, string Name, string Type)>(@$" select @@ -29,8 +63,6 @@ information_schema.routines r ( $1 is null or (r.specific_schema similar to $1) ) and ( $2 is null or r.specific_schema not similar to $2 ) and ( {GetSchemaExpression("r.specific_schema")} ) - - ") .Select(t => new PgItem { @@ -44,5 +76,6 @@ information_schema.routines r _ => PgType.Unknown } }); + */ } } diff --git a/PgRoutiner/DataAccess/GetSchemas.cs b/PgRoutiner/DataAccess/GetSchemas.cs index 3df9be0..52cd8e8 100644 --- a/PgRoutiner/DataAccess/GetSchemas.cs +++ b/PgRoutiner/DataAccess/GetSchemas.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; namespace PgRoutiner.DataAccess; @@ -8,6 +7,20 @@ public static partial class DataAccessConnectionExtensions public static IEnumerable GetSchemas(this NpgsqlConnection connection, Current settings, string skipSimilar = null, string schemaSimilarTo = null, string schemaNotSimilarTo = null) { + return connection.Read([ + (schemaSimilarTo ?? settings.SchemaSimilarTo, DbType.AnsiString, null), + (schemaNotSimilarTo ?? settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (skipSimilar, DbType.AnsiString, null)], @$" + select + schema_name + from + information_schema.schemata + where + ( $1 is null or (schema_name similar to $1) ) + and ( $2 is null or schema_name not similar to $2 ) + and ( {GetSchemaExpression("schema_name")} ) + and ( $3 is null or (schema_name not similar to $3) )", r => r.Val(0)); + /* return connection .WithParameters( (schemaSimilarTo ?? settings.SchemaSimilarTo, DbType.AnsiString), @@ -25,5 +38,6 @@ public static IEnumerable GetSchemas(this NpgsqlConnection connection, and ( {GetSchemaExpression("schema_name")} ) and ( $3 is null or (schema_name not similar to $3) ) "); + */ } } diff --git a/PgRoutiner/DataAccess/GetSequences.cs b/PgRoutiner/DataAccess/GetSequences.cs index 6bb29cd..02f5b8a 100644 --- a/PgRoutiner/DataAccess/GetSequences.cs +++ b/PgRoutiner/DataAccess/GetSequences.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -8,6 +7,32 @@ public static partial class DataAccessConnectionExtensions { public static IEnumerable GetSequences(this NpgsqlConnection connection, Current settings, string skipSimilar = null) { + return connection.Read<(string Schema, string Name)>( + [ + (settings.SchemaSimilarTo, DbType.AnsiString, null), + (settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (skipSimilar, DbType.AnsiString, null) + ], @$" + select + s.sequence_schema, + s.sequence_name + from + information_schema.sequences s + where + ( $1 is null or (s.sequence_schema similar to $1) ) + and ( $2 is null or s.sequence_schema not similar to $2 ) + and ( {GetSchemaExpression("s.sequence_schema")} ) + and ( $3 is null or (sequence_name not similar to $3) ) + ", r => (r.Val(0), r.Val(1)) + ) + .Select(t => new PgItem + { + Schema = t.Schema, + Name = t.Name, + TypeName = "SEQ", + Type = PgType.Sequence + }); + /* return connection .WithParameters( (settings.SchemaSimilarTo, DbType.AnsiString), @@ -34,5 +59,6 @@ information_schema.sequences s TypeName = "SEQ", Type = PgType.Sequence }); + */ } } diff --git a/PgRoutiner/DataAccess/GetTableComments.cs b/PgRoutiner/DataAccess/GetTableComments.cs index ff5951e..eb92c27 100644 --- a/PgRoutiner/DataAccess/GetTableComments.cs +++ b/PgRoutiner/DataAccess/GetTableComments.cs @@ -1,15 +1,224 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; public static partial class DataAccessConnectionExtensions { - public static IEnumerable GetTableComments(this NpgsqlConnection connection, - Current settings, - string schema) => - connection + public static IEnumerable GetTableComments(this NpgsqlConnection connection, + Current settings, + string schema) + { + return connection + .Read<( + string Table, + bool HasPartitions, + string Column, + bool? IsPk, + string ConstraintMarkup, + string ColumnType, + bool? IsUdt, + string Nullable, + string DefaultMarkup, + string Comment + )>([ + (schema, DbType.AnsiString, null), + (settings.MdNotSimilarTo, DbType.AnsiString, null), + (settings.MdSimilarTo, DbType.AnsiString, null)], @" + with partitioned as ( + + select + nmsp_parent.nspname as parent_schema_name, + parent.relname as parent_table_name, + nmsp_child.nspname as child_schema_name, + child.relname as child_table_name + from pg_inherits + inner join pg_class parent on pg_inherits.inhparent = parent.oid + inner join pg_class child on pg_inherits.inhrelid = child.oid + inner join pg_namespace nmsp_parent on nmsp_parent.oid = parent.relnamespace + inner join pg_namespace nmsp_child on nmsp_child.oid = child.relnamespace + inner join information_schema.tables t on parent.relname = t.table_name and nmsp_parent.nspname = $1 + + ), + table_constraints as ( + + select + sub.table_name, + sub.column_name, + sub.is_pk, + string_agg( + sub.description_markup, + ', ' + order by + case + when sub.description_markup = '**PK**' then ' ' + else sub.description_markup + end + ) as description_markup + from ( + + select + csub.table_name, + csub.column_name, + max(csub.is_pk::int)::bool as is_pk, + string_agg(csub.description_markup, ', ' order by csub.is_pk desc) as description_markup + from ( + select + tc.table_name, + coalesce(kcu.column_name, ccu.column_name) as column_name, + tc.constraint_type = 'PRIMARY KEY' as is_pk, + case when tc.constraint_type = 'PRIMARY KEY' + then '**PK**' + when tc.constraint_type = 'FOREIGN KEY' + then '**FK [➝](#' || lower(ccu.table_schema || '-' || ccu.table_name || '-' || ccu.column_name) || ') `' || + case when tc.constraint_schema = ccu.table_schema + then '' + else ccu.table_schema || '.' + end + || case when ccu.table_schema = 'public' then '' else ccu.table_schema || '.' end || ccu.table_name || '.' || ccu.column_name || '`**' + when tc.constraint_type = 'CHECK' then (select '`' || pg_get_constraintdef((select oid from pg_constraint where conname = tc.constraint_name), true) || '`') + else tc.constraint_type + end as description_markup + + from + information_schema.table_constraints tc + inner join information_schema.constraint_column_usage ccu + on tc.constraint_schema = ccu.constraint_schema and tc.constraint_name = ccu.constraint_name + left outer join information_schema.key_column_usage kcu + on tc.constraint_type = 'FOREIGN KEY' and tc.constraint_schema = kcu.constraint_schema and ccu.constraint_name = kcu.constraint_name + where + tc.constraint_schema = $1 + group by + tc.table_name, kcu.column_name, ccu.column_name, tc.constraint_name, + tc.constraint_type, ccu.table_schema, ccu.table_name, tc.constraint_schema + ) csub + group by + csub.table_name, + csub.column_name + + union all + + select + i.relname as table_name, + a.attname as column_name, + false as is_pk, + '**IDX**' as description_markup + from + pg_stat_all_indexes i + inner join pg_attribute a on i.indexrelid = a.attrelid + left outer join information_schema.table_constraints tc on i.indexrelname = tc.constraint_name + where tc.table_name is null and i.schemaname = $1 + + order by + description_markup + + ) sub + group by + sub.table_name, sub.column_name, sub.is_pk + + ) + select + table_name_id as table_name, + ( + select exists (select 1 from partitioned where partitioned.parent_table_name = table_name_id) + ) as has_partitions, + c.column_name, + tc.is_pk, + tc.description_markup, + + case when c.data_type = 'USER-DEFINED' + then c.udt_name + else case when c.udt_schema <> 'pg_catalog' + then c.udt_schema || '.' + else '' + end || c.udt_name::regtype + end || + case when c.data_type <> 'integer' and c.data_type <> 'bigint' and c.data_type <> 'smallint' + then + case when c.character_maximum_length is not null + then '(' || cast(c.character_maximum_length as varchar) || ')' + else + case when c.numeric_precision is not null + then '(' || cast(c.numeric_precision as varchar) || ',' || cast(c.numeric_scale as varchar) || ')' + else '' + end + end + else '' + end as data_type, + + c.data_type = 'USER-DEFINED' as is_udt, + + case when c.is_nullable = 'NO' then '**NO**' else c.is_nullable end as nullableMarkup, + + '`' || case + when c.identity_generation is not null then 'GENERATED ' || c.identity_generation || ' AS IDENTITY' + when c.is_generated <> 'NEVER' then 'GENERATED ' || c.is_generated || ' AS ' || c.generation_expression + else c.column_default + end + || '`' as defaultMarkup, + + pgdesc.description + + from ( + select t1.table_name as table_name_id, t1.table_name + from information_schema.tables t1 + where t1.table_schema = $1 and t1.table_type = 'BASE TABLE' + union all + select t2.table_name as table_name_id, null as table_name + from information_schema.tables t2 + where t2.table_schema = $1 and t2.table_type = 'BASE TABLE' + order by table_name_id, table_name nulls first + ) t + + left outer join information_schema.columns c + on t.table_name = c.table_name and c.table_schema = $1 + + left outer join pg_catalog.pg_stat_all_tables pgtbl + on t.table_name_id = pgtbl.relname and pgtbl.schemaname = $1 + + left outer join pg_catalog.pg_description pgdesc + on pgtbl.relid = pgdesc.objoid and coalesce(c.ordinal_position, 0) = pgdesc.objsubid + + left outer join table_constraints tc + on t.table_name = tc.table_name and c.column_name = tc.column_name + + left outer join partitioned part + on table_name_id = part.child_table_name and pgtbl.schemaname = part.child_schema_name + + where + part.child_table_name is null + and ($2 is null or table_name_id not similar to $2) + and ($3 is null or table_name_id similar to $3) + + order by + t.table_name_id, + t.table_name nulls first, + c.ordinal_position", r => ( + r.Val(0), + r.Val(1), + r.Val(2), + r.Val(3), + r.Val(4), + r.Val(5), + r.Val(6), + r.Val(7), + r.Val(8), + r.Val(9))) + .Select(t => new TableComment + { + Column = t.Column, + IsPk = t.IsPk, + ColumnType = t.ColumnType, + IsUdt = t.IsUdt, + Comment = t.Comment, + ConstraintMarkup = t.ConstraintMarkup, + DefaultMarkup = t.DefaultMarkup, + Nullable = t.Nullable, + Table = t.Table, + HasPartitions = t.HasPartitions, + }); + /* + return connection .WithParameters( (schema, DbType.AnsiString), (settings.MdNotSimilarTo, DbType.AnsiString), @@ -199,11 +408,191 @@ order by Table = t.Table, HasPartitions = t.HasPartitions, }); + */ + } public static IEnumerable GetViewComments(this NpgsqlConnection connection, Current settings, - string schema) => - connection + string schema) + { + return connection.Read<( + string Table, + bool HasPartitions, + string Column, + bool? IsPk, + string ConstraintMarkup, + string ColumnType, + bool? IsUdt, + string Nullable, + string DefaultMarkup, + string Comment)>([ + (schema, DbType.AnsiString, null), + (settings.MdNotSimilarTo, DbType.AnsiString, null), + (settings.MdSimilarTo, DbType.AnsiString, null) + ], @" + with table_constraints as ( + + select + sub.table_name, + sub.column_name, + sub.is_pk, + string_agg( + sub.description_markup, + ', ' + order by + case + when sub.description_markup = '**PK**' then ' ' + else sub.description_markup + end + ) as description_markup + from ( + select + tc.table_name, + coalesce(kcu.column_name, ccu.column_name) as column_name, + tc.constraint_type = 'PRIMARY KEY' as is_pk, + case when tc.constraint_type = 'PRIMARY KEY' + then '**PK**' + when tc.constraint_type = 'FOREIGN KEY' + then '**FK [➝](#' || lower(ccu.table_schema || '-' || ccu.table_name || '-' || ccu.column_name) || ') `' || + case when tc.constraint_schema = ccu.table_schema + then '' + else ccu.table_schema || '.' + end + || ccu.table_name || '.' || ccu.column_name || '`**' + when tc.constraint_type = 'CHECK' then (select '`' || pg_get_constraintdef((select oid from pg_constraint where conname = tc.constraint_name), true) || '`') + else tc.constraint_type + end as description_markup + + from + information_schema.table_constraints tc + inner join information_schema.constraint_column_usage ccu + on tc.constraint_schema = ccu.constraint_schema and tc.constraint_name = ccu.constraint_name + left outer join information_schema.key_column_usage kcu + on tc.constraint_type = 'FOREIGN KEY' and tc.constraint_schema = kcu.constraint_schema and ccu.constraint_name = kcu.constraint_name + where + tc.constraint_schema = $1 + group by + tc.table_name, kcu.column_name, ccu.column_name, tc.constraint_name, + tc.constraint_type, ccu.table_schema, ccu.table_name, tc.constraint_schema + + union all + + select + i.relname as table_name, + a.attname as column_name, + false as is_pk, + '**IDX**' as description_markup + from + pg_stat_all_indexes i + inner join pg_attribute a on i.indexrelid = a.attrelid + left outer join information_schema.table_constraints tc on i.indexrelname = tc.constraint_name + where tc.table_name is null and i.schemaname = $1 + + order by + description_markup + + ) sub + group by + sub.table_name, sub.column_name, sub.is_pk + + ) + select + table_name_id as table_name, + false as has_partitions, + c.column_name, + tc.is_pk, + tc.description_markup, + + case when c.data_type = 'USER-DEFINED' + then c.udt_name + else case when c.udt_schema <> 'pg_catalog' + then c.udt_schema || '.' + else '' + end || c.udt_name::regtype + end || + case when c.data_type <> 'integer' and c.data_type <> 'bigint' and c.data_type <> 'smallint' + then + case when c.character_maximum_length is not null + then '(' || cast(c.character_maximum_length as varchar) || ')' + else + case when c.numeric_precision is not null + then '(' || cast(c.numeric_precision as varchar) || ',' || cast(c.numeric_scale as varchar) || ')' + else '' + end + end + else '' + end as data_type, + + c.data_type = 'USER-DEFINED' as is_udt, + + case when c.is_nullable = 'NO' then '**NO**' else c.is_nullable end as nullableMarkup, + + '`' || case + when c.identity_generation is not null then 'GENERATED ' || c.identity_generation || ' AS IDENTITY' + when c.is_generated <> 'NEVER' then 'GENERATED ' || c.is_generated || ' AS ' || c.generation_expression + else c.column_default + end + || '`' as defaultMarkup, + + pgdesc.description + + from ( + select t1.table_name as table_name_id, t1.table_name + from information_schema.tables t1 + where t1.table_schema = $1 and t1.table_type = 'VIEW' + union all + select t2.table_name as table_name_id, null as table_name + from information_schema.tables t2 + where t2.table_schema = $1 and t2.table_type = 'VIEW' + order by table_name_id, table_name nulls first + ) t + + left outer join information_schema.columns c + on t.table_name = c.table_name and c.table_schema = $1 + + left outer join pg_catalog.pg_statio_user_tables pgtbl + on t.table_name_id = pgtbl.relname and pgtbl.schemaname = $1 + + left outer join pg_catalog.pg_description pgdesc + on pgtbl.relid = pgdesc.objoid and coalesce(c.ordinal_position, 0) = pgdesc.objsubid + + left outer join table_constraints tc + on t.table_name = tc.table_name and c.column_name = tc.column_name + + where + ($2 is null or table_name_id not similar to $2) + and ($3 is null or table_name_id similar to $3) + + order by + t.table_name_id, + t.table_name nulls first, + c.ordinal_position + ", r => (r.Val(0), + r.Val(1), + r.Val(2), + r.Val(3), + r.Val(4), + r.Val(5), + r.Val(6), + r.Val(7), + r.Val(8), + r.Val(9))) + + .Select(t => new TableComment + { + Column = t.Column, + IsPk = t.IsPk, + ColumnType = t.ColumnType, + IsUdt = t.IsUdt, + Comment = t.Comment, + ConstraintMarkup = t.ConstraintMarkup, + DefaultMarkup = t.DefaultMarkup, + Nullable = t.Nullable, + Table = t.Table, + HasPartitions = t.HasPartitions, + }); + /* + return connection .WithParameters( (schema, DbType.AnsiString), (settings.MdNotSimilarTo, DbType.AnsiString), @@ -361,5 +750,6 @@ order by Table = t.Table, HasPartitions = t.HasPartitions, }); - + */ + } } diff --git a/PgRoutiner/DataAccess/GetTableDefintions.cs b/PgRoutiner/DataAccess/GetTableDefintions.cs index 431b189..bd82972 100644 --- a/PgRoutiner/DataAccess/GetTableDefintions.cs +++ b/PgRoutiner/DataAccess/GetTableDefintions.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -9,6 +8,102 @@ public static partial class DataAccessConnectionExtensions public static IEnumerable> GetTableDefintions(this NpgsqlConnection connection, Current settings, string tableExpr) { + return connection.Read<( + string Schema, + string Table, + string Name, + int Ord, + bool IsGeneration, + string Default, + string IsNullable, + string DataType, + string TypeUdtName, + string IsIdentity, + string[] ConstraintTypes)>([ + (settings.SchemaSimilarTo, DbType.AnsiString, null), + (settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (tableExpr, DbType.AnsiString, null) + ], @$" + + select + t.table_schema, + t.table_name, + c.column_name, + c.ordinal_position, + + c.generation_expression is not null as is_generation_expression, + c.column_default as column_default, + + c.is_nullable, + c.data_type, + --c.udt_name, + regexp_replace(c.udt_name, '^[_]', '') as udt_name, + c.is_identity, + array_agg(tc.constraint_type) as constraint_types + from + information_schema.tables t + inner join information_schema.columns c + on t.table_name = c.table_name and t.table_schema = c.table_schema + + left outer join information_schema.key_column_usage kcu + on t.table_name = kcu.table_name and t.table_schema = kcu.table_schema and c.column_name = kcu.column_name + + left outer join information_schema.table_constraints tc + on t.table_name = tc.table_name and t.table_schema = tc.table_schema and tc.constraint_name = kcu.constraint_name + where + table_type = 'BASE TABLE' + and (t.table_name similar to $3 or t.table_schema || '.' || t.table_name similar to $3) + and + ( $1 is null or (t.table_schema similar to $1) ) + and ( $2 is null or (t.table_schema not similar to $2) ) + and ( {GetSchemaExpression("t.table_schema")} ) + group by + t.table_schema, + t.table_name, + c.column_name, + c.ordinal_position, + c.identity_generation, + c.is_generated, + c.generation_expression, + c.column_default, + c.is_nullable, + c.data_type, + c.udt_name, + c.is_identity + order by + t.table_schema, + t.table_name, + c.ordinal_position + + ", r => (r.Val(0), + r.Val(1), + r.Val(2), + r.Val(3), + r.Val(4), + r.Val(5), + r.Val(6), + r.Val(7), + r.Val(8), + r.Val(9), + r.Val(10))) + .Select(t => new PgColumnGroup + { + Schema = t.Schema, + Table = t.Table, + Name = t.Name, + Ordinal = t.Ord, + IsGeneration = t.IsGeneration, + Default = t.Default, + HasDefault = !string.IsNullOrEmpty(t.Default), + IsNullable = string.Equals(t.IsNullable, "YES"), + DataType = t.DataType, + Type = t.TypeUdtName, + IsArray = t.DataType == "ARRAY",//t.ConstraintTypes.Contains("ARRAY"), + IsIdentity = string.Equals(t.IsIdentity, "YES"), + IsPk = t.ConstraintTypes.Contains("PRIMARY KEY"), + }) + .GroupBy(i => (i.Schema, i.Table)); + /* return connection .WithParameters( (settings.SchemaSimilarTo, DbType.AnsiString), @@ -95,5 +190,6 @@ order by IsPk = t.ConstraintTypes.Contains("PRIMARY KEY"), }) .GroupBy(i => (i.Schema, i.Table)); + */ } } diff --git a/PgRoutiner/DataAccess/GetTableEstimatedCount.cs b/PgRoutiner/DataAccess/GetTableEstimatedCount.cs index c7d00bd..0e1ec6e 100644 --- a/PgRoutiner/DataAccess/GetTableEstimatedCount.cs +++ b/PgRoutiner/DataAccess/GetTableEstimatedCount.cs @@ -1,6 +1,4 @@ -using System.Data; -using Norm; -using NpgsqlTypes; +using NpgsqlTypes; namespace PgRoutiner.DataAccess; @@ -8,6 +6,20 @@ public static partial class DataAccessConnectionExtensions { public static long GetTableEstimatedCount(this NpgsqlConnection connection, string schema, string table) { + return connection.Read( + [ + (table, null, NpgsqlDbType.Text), + (schema, null, NpgsqlDbType.Text) + ], @" + select reltuples::bigint + from + pg_class a + inner join pg_namespace b on a.relnamespace = b.oid + where + relname::text = $1 and nspname::text = $2 + ", r => r.Val(0)) + .FirstOrDefault(); + /* return connection .WithParameters(new { @@ -24,5 +36,6 @@ pg_class a relname::text = @table and nspname::text = @schema ").FirstOrDefault(); + */ } } diff --git a/PgRoutiner/DataAccess/GetTableStats.cs b/PgRoutiner/DataAccess/GetTableStats.cs index b450607..0212543 100644 --- a/PgRoutiner/DataAccess/GetTableStats.cs +++ b/PgRoutiner/DataAccess/GetTableStats.cs @@ -1,6 +1,4 @@ -using System.Data; -using Norm; -using NpgsqlTypes; +using NpgsqlTypes; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -9,6 +7,54 @@ public static partial class DataAccessConnectionExtensions { public static PgTableStats GetTableStats(this NpgsqlConnection connection, string schema, string table) { + return connection.Read( + [ + (table, null, NpgsqlDbType.Text), + (schema, null, NpgsqlDbType.Text) + ], @" + + select + seq_scan as seq_scan_count, + seq_tup_read as seq_scan_rows, + idx_scan as idx_scan_count, + idx_tup_fetch as idx_scan_rows, + n_tup_ins as rows_inserted, + n_tup_upd as rows_updated, + n_tup_del as rows_deleted, + n_live_tup as live_rows, + n_dead_tup as dead_rows, + n_mod_since_analyze as rows_modified_since_analyze, + n_ins_since_vacuum as rows_inserted_since_vacuum, + last_vacuum, + vacuum_count, + last_analyze, + analyze_count, + last_autoanalyze, + last_autovacuum + from + pg_stat_all_tables + where + relname= $1 and schemaname = $2", r => new PgTableStats + { + SeqScanCount = r.Val(0), + SeqScanRows = r.Val(1), + IdxScanCount = r.Val(2), + IdxScanRows = r.Val(3), + RowsInserted = r.Val(4), + RowsUpdated = r.Val(5), + RowsDeleted = r.Val(6), + LiveRows = r.Val(7), + DeadRows = r.Val(8), + RowsModifiedSinceAnalyze = r.Val(9), + RowsInsertedSinceVacuum = r.Val(10), + LastVacuum = r.Val(11), + VacuumCount = r.Val(12), + LastAnalyze = r.Val(13), + AnalyzeCount = r.Val(14), + LastAutoanalyze = r.Val(15), + LastAutovacuum = r.Val(16) + }).FirstOrDefault(); + /* return connection .WithParameters(new { @@ -41,5 +87,6 @@ public static PgTableStats GetTableStats(this NpgsqlConnection connection, strin relname= @table and schemaname = @schema ").FirstOrDefault(); + */ } } diff --git a/PgRoutiner/DataAccess/GetTables.cs b/PgRoutiner/DataAccess/GetTables.cs index c99edf3..63a8c06 100644 --- a/PgRoutiner/DataAccess/GetTables.cs +++ b/PgRoutiner/DataAccess/GetTables.cs @@ -1,5 +1,4 @@ using System.Data; -using Norm; using PgRoutiner.DataAccess.Models; namespace PgRoutiner.DataAccess; @@ -8,6 +7,47 @@ public static partial class DataAccessConnectionExtensions { public static IEnumerable GetTables(this NpgsqlConnection connection, Current settings, string skipSimilar = null) { + return connection.Read<(string Schema, string Name, string Type)>([ + (settings.SchemaSimilarTo, DbType.AnsiString, null), + (settings.SchemaNotSimilarTo, DbType.AnsiString, null), + (skipSimilar, DbType.AnsiString, null) + ], @$" + select + t.table_schema, t.table_name, t.table_type + from + information_schema.tables t + where + ( $1 is null or (table_schema similar to $1) ) + and ( $2 is null or (table_schema not similar to $2) ) + and ( {GetSchemaExpression("table_schema")} ) + and ( $3 is null or (table_name not similar to $3) ) + and not exists ( + + select + 1 + from + pg_inherits + inner join pg_class parent on pg_inherits.inhparent = parent.oid + inner join pg_class child on pg_inherits.inhrelid = child.oid + inner join pg_namespace nmsp_parent on nmsp_parent.oid = parent.relnamespace + inner join pg_namespace nmsp_child on nmsp_child.oid = child.relnamespace + where + nmsp_child.nspname = t.table_schema and child.relname = t.table_name + ) + ", r => (r.Val(0), r.Val(1), r.Val(2))) + .Select(t => new PgItem + { + Schema = t.Schema, + Name = t.Name, + TypeName = t.Type, + Type = t.Type switch + { + "BASE TABLE" => PgType.Table, + "VIEW" => PgType.View, + _ => PgType.Unknown + } + }); + /* return connection .WithParameters( (settings.SchemaSimilarTo, DbType.AnsiString), @@ -50,5 +90,6 @@ and not exists ( _ => PgType.Unknown } }); + */ } } diff --git a/PgRoutiner/DataAccess/GetTablesThatDontExist.cs b/PgRoutiner/DataAccess/GetTablesThatDontExist.cs index 6787b53..7d4430d 100644 --- a/PgRoutiner/DataAccess/GetTablesThatDontExist.cs +++ b/PgRoutiner/DataAccess/GetTablesThatDontExist.cs @@ -1,6 +1,4 @@ -using Norm; -using NpgsqlTypes; -using PgRoutiner.Builder.DiffBuilder; +using NpgsqlTypes; namespace PgRoutiner.DataAccess; @@ -8,8 +6,23 @@ public static partial class DataAccessConnectionExtensions { public static IEnumerable GetTablesThatDontExist(this NpgsqlConnection connection, IEnumerable tableNames) { + return connection.Read([(tableNames.ToList(), null, NpgsqlDbType.Varchar | NpgsqlDbType.Array)], @" + select + n + from + unnest($1) n + left outer join information_schema.tables t + on + t.table_name = n + or '""' || t.table_name || '""' = n + or t.table_schema || '.' || t.table_name = n + or t.table_schema || '.' || '""' || t.table_name || '""' = n + where + t.table_name is null + ", r => r.Val(0)); + /* return connection - .WithParameters((tableNames, NpgsqlDbType.Varchar | NpgsqlDbType.Array)) + .WithParameters((tableNames.ToList(), NpgsqlDbType.Varchar | NpgsqlDbType.Array)) .Read(@$" select @@ -26,5 +39,6 @@ left outer join information_schema.tables t t.table_name is null "); + */ } } diff --git a/PgRoutiner/DataAccess/JsonOptions.cs b/PgRoutiner/DataAccess/JsonOptions.cs new file mode 100644 index 0000000..85f767a --- /dev/null +++ b/PgRoutiner/DataAccess/JsonOptions.cs @@ -0,0 +1,6 @@ +namespace PgRoutiner.DataAccess +{ + public class JsonOptions + { + } +} \ No newline at end of file diff --git a/PgRoutiner/DataAccess/Models/PgParameter.cs b/PgRoutiner/DataAccess/Models/PgParameter.cs index 43b2738..1f0c51f 100644 --- a/PgRoutiner/DataAccess/Models/PgParameter.cs +++ b/PgRoutiner/DataAccess/Models/PgParameter.cs @@ -1,4 +1,6 @@ -namespace PgRoutiner.DataAccess.Models; +using System.Text.Json.Serialization; + +namespace PgRoutiner.DataAccess.Models; public class PgParameter { diff --git a/PgRoutiner/DataAccess/_Orm.cs b/PgRoutiner/DataAccess/_Orm.cs new file mode 100644 index 0000000..2427d14 --- /dev/null +++ b/PgRoutiner/DataAccess/_Orm.cs @@ -0,0 +1,51 @@ +using System.Data; +using NpgsqlTypes; + +namespace PgRoutiner.DataAccess; + +public static class _Orm +{ + public static IEnumerable Read(this NpgsqlConnection connection, + IEnumerable<(object value, DbType? dbType, NpgsqlDbType? npgsqlDbType)> parameters, + string command, + Func action) + { + if (connection.State != ConnectionState.Open) connection.Open(); + using var cmd = connection.CreateCommand(); + cmd.CommandText = command; + foreach(var (value, dbType, npgsqlDbType) in parameters) + { + var p = cmd.CreateParameter(); + p.Value = value ?? DBNull.Value; + if (dbType.HasValue) p.DbType = dbType.Value; + if (npgsqlDbType.HasValue) p.NpgsqlDbType = npgsqlDbType.Value; + cmd.Parameters.Add(p); + } + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + { + yield return action(reader); + } + reader.Close(); + } + + public static void Execute(this NpgsqlConnection connection, string command) + { + if (connection.State != ConnectionState.Open) connection.Open(); + using var cmd = connection.CreateCommand(); + cmd.CommandText = command; + cmd.ExecuteNonQuery(); + } + + public static T Val(this NpgsqlDataReader reader, string name) + { + var value = reader[name]; + return value == DBNull.Value ? default : (T)value; + } + + public static T Val(this NpgsqlDataReader reader, int ordinal) + { + var value = reader[ordinal]; + return value == DBNull.Value ? default : (T)value; + } +} diff --git a/PgRoutiner/Extensions.cs b/PgRoutiner/Extensions.cs index f372b05..0e2ecfc 100644 --- a/PgRoutiner/Extensions.cs +++ b/PgRoutiner/Extensions.cs @@ -68,7 +68,11 @@ public static string FirstWordAfter(this string value, string word, char? charac } if (value.Length == 0) { - return string.Empty; + return null; + } + if (value.Contains('\'')) + { + return null; } var index = value.IndexOf(word); if (index == -1) diff --git a/PgRoutiner/PgRoutiner.csproj b/PgRoutiner/PgRoutiner.csproj index 6010f4e..889caa7 100644 --- a/PgRoutiner/PgRoutiner.csproj +++ b/PgRoutiner/PgRoutiner.csproj @@ -14,12 +14,15 @@ https://github.com/vb-consulting/PgRoutiner PostgreSQL true - 5.3.7.0 + 5.4.0.0 dotnet-pgroutiner - 5.3.7.0 - 5.3.7.0 + 5.4.0.0 + 5.4.0.0 Debug;Release;SelfContained LICENSE + true + pgroutiner + true @@ -44,15 +47,14 @@ - - - - - - - - + + + + + + + diff --git a/PgRoutiner/Program.cs b/PgRoutiner/Program.cs index 5887fbb..42367ef 100644 --- a/PgRoutiner/Program.cs +++ b/PgRoutiner/Program.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Reflection; +using System.Text.Json; using Microsoft.Extensions.Configuration; using PgRoutiner.Builder; using PgRoutiner.Connection; @@ -36,6 +37,9 @@ static void Main(string[] rawArgs) //rawArgs = new string[] { "--settings", "--list", "--definition" }; //rawArgs = new string[] { "--search", "film" }; //rawArgs = new string[] { "--inserts", "select b.first_name || ' ' || b.last_name as actor, array_agg(c.title) as titles from film_actor a inner join actor b on a.actor_id = b.actor_id inner join film c on a.film_id = c.film_id group by b.first_name || ' ' || b.last_name" }; + //rawArgs = new string[] { "-l" }; + //rawArgs = new string[] { "-l", "film_in" }; + //rawArgs = new string[] { "-r", "-d" }; var args = ParseArgs(rawArgs); @@ -148,9 +152,9 @@ static void Main(string[] rawArgs) return; } - //WriteLine(""); + WriteLine(""); Runner.Run(connection); - //WriteLine(""); + WriteLine(""); } public static string Version @@ -193,7 +197,7 @@ public static bool Docker } } - private class DirSettings + public class DirSettings { public string Dir { get; set; } = null; } @@ -204,9 +208,9 @@ public static bool SetCurrentDir(string[] args) new Dictionary { { Current.DirArgs.Alias, Current.DirArgs.Name.Replace("--", "") } }); - var config = configBuilder.Build(); + var ds = new DirSettings(); - config.Bind(ds); + configBuilder.Build().Bind(ds); if (!string.IsNullOrEmpty(ds.Dir)) { CurrentDir = Path.Join(CurrentDir, ds.Dir); diff --git a/PgRoutiner/SettingsManagement/Settings.cs b/PgRoutiner/SettingsManagement/Settings.cs index 7b76426..47e27c2 100644 --- a/PgRoutiner/SettingsManagement/Settings.cs +++ b/PgRoutiner/SettingsManagement/Settings.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace PgRoutiner.SettingsManagement { diff --git a/build.bat b/build.bat deleted file mode 100644 index abf5f19..0000000 --- a/build.bat +++ /dev/null @@ -1,20 +0,0 @@ -cd PgRoutiner - -REM starting main build... see more on targets on rid catalog: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog -REM ******************************************************************************************************************* - -REM cleaning solution -REM ***************** -dotnet clean --configuration SelfContained - -REM win10-x64 publish -REM ***************** -dotnet publish --runtime win10-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true /p:PublishReadyToRun=true --self-contained --output "_exe\win10-x64" - -REM linux-x64 publish -REM ***************** -dotnet publish --runtime linux-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained --output "_exe\linux-x64" - -REM osx-x64 publish -REM ***************** -dotnet publish --runtime osx-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained --output "_exe\osx-x64" diff --git a/build.sh b/build.sh deleted file mode 100644 index 8d77062..0000000 --- a/build.sh +++ /dev/null @@ -1,24 +0,0 @@ -cd PgRoutiner - -echo starting main build... see more on targets on rid catalog: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog -echo ******************************************************************************************************************* - -echo cleaning solution -echo ***************** -echo dotnet clean --configuration SelfContained -dotnet clean --configuration SelfContained - -echo win10-x64 publish -echo ***************** -echo dotnet publish --runtime win10-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true /p:PublishReadyToRun=true --self-contained --output "_exe\win10-x64" -dotnet publish --runtime win10-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true /p:PublishReadyToRun=true --self-contained --output "_exe\win10-x64" - -echo linux-x64 publish -echo ***************** -echo dotnet publish -r linux-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained --output "_exe\linux-x64" -dotnet publish --runtime linux-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained --output "_exe\linux-x64" - -echo osx-x64 publish -echo ***************** -echo dotnet publish -r osx-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained --output "_exe\osx-x64" -dotnet publish --runtime osx-x64 --configuration SelfContained /p:PublishSingleFile=true /p:PublishTrimmed=true --self-contained --output "_exe\osx-x64" diff --git a/changelog.md b/changelog.md index c6a3c37..1f8c54e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,9 @@ # VERSION HISTORY -## 5.3.8 -## 5.3.8 +## 5.4.0 -- Upgrade to .net 8 +- Upgrade to .NET8 +- Make project AOT ready and prepare and test AOT builds. ## 5.3.7