using NpgsqlTypes; namespace NpgsqlRest; public class TypeDescriptor { public string OriginalType { get; } public string Type { get; } public bool IsArray { get; } public NpgsqlDbType DbType { get; } public NpgsqlDbType BaseDbType { get; } public NpgsqlDbType ActualDbType { get; } public bool HasDefault { get; private set; } public bool IsPk { get; } public bool IsIdentity { get; } public string? CustomType { get; } public short? CustomTypePosition { get; } public string? OriginalParameterName { get; } public string? CustomTypeName { get; } internal bool ShouldRenderAsUnknownType => IsBinary is false; /// /// Pre-computed type category flags for optimized type dispatch. /// Use bitwise operations for fast type checking in hot paths. /// public TypeCategory Category { get; } // Computed properties from Category for backward compatibility public bool IsNumeric => (Category & TypeCategory.Numeric) != 0; public bool IsJson => (Category & TypeCategory.Json) != 0; public bool IsDate => (Category & TypeCategory.Date) != 0; public bool IsDateTime => (Category & TypeCategory.DateTime) != 0; public bool IsBoolean => (Category & TypeCategory.Boolean) != 0; public bool IsText => (Category & TypeCategory.Text) != 0; public bool NeedsEscape { get; } public bool IsBinary => (Category & TypeCategory.Binary) != 0; // Properties for nested composite type support /// /// For fields that are composite types, the converted field names of the nested type. /// Null if this is not a composite type field. /// public string[]? CompositeFieldNames { get; internal set; } /// /// For fields that are composite types, the type descriptors for each nested field. /// Null if this is not a composite type field. /// public TypeDescriptor[]? CompositeFieldDescriptors { get; internal set; } /// /// For fields that are arrays of composite types, the converted field names of the element type. /// Null if this is not an array of composite type field. /// public string[]? ArrayCompositeFieldNames { get; internal set; } /// /// For fields that are arrays of composite types, the type descriptors for each element field. /// Null if this is not an array of composite type field. /// public TypeDescriptor[]? ArrayCompositeFieldDescriptors { get; internal set; } /// /// True if this field is a composite type with resolved nested field metadata. /// public bool IsCompositeType => CompositeFieldNames != null; /// /// True if this field is an array of composite types with resolved nested field metadata. /// public bool IsArrayOfCompositeType => ArrayCompositeFieldNames != null; public TypeDescriptor( string type, bool hasDefault = false, bool isPk = false, bool isIdentity = false, string? customType = null, short? customTypePosition = null, string? originalParameterName = null, string? customTypeName = null) { OriginalType = type; HasDefault = hasDefault; IsPk = isPk; IsIdentity = isIdentity; IsArray = type.EndsWith("[]"); Type = (IsArray ? type[..^2] : type).Trim(Consts.DoubleQuote); DbType = GetDbType(); BaseDbType = DbType; // Use pre-computed lookup table for type category Category = TypeCategoryLookup.GetCategory(BaseDbType); ActualDbType = (Category & TypeCategory.CastToText) != 0 ? NpgsqlDbType.Text : BaseDbType; if (IsArray) { DbType |= NpgsqlDbType.Array; ActualDbType |= NpgsqlDbType.Array; } NeedsEscape = (Category & TypeCategory.NeedsEscape) != 0; CustomType = customType; CustomTypePosition = customTypePosition; OriginalParameterName = originalParameterName; CustomTypeName = customTypeName; } internal void SetHasDefault() { HasDefault = true; } public bool IsCastToText() => (Category & TypeCategory.CastToText) != 0; private NpgsqlDbType GetDbType() { // Strip type modifiers (length, precision, scale) before matching // e.g., "character(1)" -> "character", "numeric(10,2)" -> "numeric" var normalizedType = Type; var parenIndex = Type.IndexOf('('); if (parenIndex > 0) { normalizedType = Type.Substring(0, parenIndex); } var result = normalizedType switch { "smallint" => NpgsqlDbType.Smallint, "int" => NpgsqlDbType.Integer, "integer" => NpgsqlDbType.Integer, "bigint" => NpgsqlDbType.Bigint, "decimal" => NpgsqlDbType.Numeric, "numeric" => NpgsqlDbType.Numeric, "real" => NpgsqlDbType.Real, "double precision" => NpgsqlDbType.Double, "float" => NpgsqlDbType.Double, "int2" => NpgsqlDbType.Smallint, "int4" => NpgsqlDbType.Integer, "int8" => NpgsqlDbType.Bigint, "float4" => NpgsqlDbType.Real, "float8" => NpgsqlDbType.Double, "money" => NpgsqlDbType.Money, "smallserial" => NpgsqlDbType.Smallint, "serial" => NpgsqlDbType.Integer, "serial2" => NpgsqlDbType.Smallint, "serial4" => NpgsqlDbType.Integer, "bigserial" => NpgsqlDbType.Bigint, "serial8" => NpgsqlDbType.Bigint, "text" => NpgsqlDbType.Text, "xml" => NpgsqlDbType.Xml, "varchar" => NpgsqlDbType.Varchar, "character varying" => NpgsqlDbType.Varchar, "bpchar" => NpgsqlDbType.Char, "character" => NpgsqlDbType.Char, "char" => NpgsqlDbType.Char, "name" => NpgsqlDbType.Name, "refcursor" => NpgsqlDbType.Refcursor, "jsonb" => NpgsqlDbType.Jsonb, "json" => NpgsqlDbType.Json, "jsonpath" => NpgsqlDbType.JsonPath, "timestamp" => NpgsqlDbType.Timestamp, "timestamptz" => NpgsqlDbType.TimestampTz, "timestamp without time zone" => NpgsqlDbType.Timestamp, "timestamp with time zone" => NpgsqlDbType.TimestampTz, "date" => NpgsqlDbType.Date, "time" => NpgsqlDbType.Time, "timetz" => NpgsqlDbType.TimeTz, "time without time zone" => NpgsqlDbType.Time, "time with time zone" => NpgsqlDbType.TimeTz, "interval" => NpgsqlDbType.Interval, "bool" => NpgsqlDbType.Boolean, "boolean" => NpgsqlDbType.Boolean, "bytea" => NpgsqlDbType.Bytea, "uuid" => NpgsqlDbType.Uuid, "bit varying" => NpgsqlDbType.Varbit, "varbit" => NpgsqlDbType.Varbit, "bit" => NpgsqlDbType.Bit, "cidr" => NpgsqlDbType.Cidr, "inet" => NpgsqlDbType.Inet, "macaddr" => NpgsqlDbType.MacAddr, "macaddr8" => NpgsqlDbType.MacAddr8, "tsquery" => NpgsqlDbType.TsQuery, "tsvector" => NpgsqlDbType.TsVector, "box" => NpgsqlDbType.Box, "circle" => NpgsqlDbType.Circle, "line" => NpgsqlDbType.Line, "lseg" => NpgsqlDbType.LSeg, "path" => NpgsqlDbType.Path, "point" => NpgsqlDbType.Point, "polygon" => NpgsqlDbType.Polygon, "oid" => NpgsqlDbType.Oid, "xid" => NpgsqlDbType.Xid, "xid8" => NpgsqlDbType.Xid8, "cid" => NpgsqlDbType.Cid, "regtype" => NpgsqlDbType.Regtype, "regconfig" => NpgsqlDbType.Regconfig, "int4range" => NpgsqlDbType.IntegerRange, "int8range" => NpgsqlDbType.BigIntRange, "numrange" => NpgsqlDbType.NumericRange, "tsrange" => NpgsqlDbType.TimestampRange, "tstzrange" => NpgsqlDbType.TimestampTzRange, "daterange" => NpgsqlDbType.DateRange, "int4multirange" => NpgsqlDbType.IntegerMultirange, "int8multirange" => NpgsqlDbType.BigIntMultirange, "nummultirange" => NpgsqlDbType.NumericMultirange, "tsmultirange" => NpgsqlDbType.TimestampMultirange, "tstzmultirange" => NpgsqlDbType.TimestampTzMultirange, "datemultirange" => NpgsqlDbType.DateMultirange, "int2vector" => NpgsqlDbType.Int2Vector, "oidvector" => NpgsqlDbType.Oidvector, "pg_lsn" => NpgsqlDbType.PgLsn, "tid" => NpgsqlDbType.Tid, "citext" => NpgsqlDbType.Citext, "lquery" => NpgsqlDbType.LQuery, "ltree" => NpgsqlDbType.LTree, "ltxtquery" => NpgsqlDbType.LTxtQuery, "hstore" => NpgsqlDbType.Hstore, "geometry" => NpgsqlDbType.Geometry, "geography" => NpgsqlDbType.Geography, _ => NpgsqlDbType.Unknown }; return result; } }