diff --git a/src/SQLProvider.Common/Operators.fs b/src/SQLProvider.Common/Operators.fs index 612db768..2c1aa866 100644 --- a/src/SQLProvider.Common/Operators.fs +++ b/src/SQLProvider.Common/Operators.fs @@ -2,23 +2,43 @@ namespace FSharp.Data.Sql open System.Linq +/// +/// Represents SQL conditional operators used in WHERE clauses and query expressions. +/// These operators are translated to their SQL equivalents when building database queries. +/// [] type ConditionOperator = + /// SQL LIKE operator for pattern matching with wildcards | Like + /// SQL NOT LIKE operator for negated pattern matching | NotLike + /// SQL equality operator (=) | Equal + /// SQL inequality operator (<>) | NotEqual + /// SQL greater than operator (>) | GreaterThan + /// SQL less than operator (<) | LessThan + /// SQL greater than or equal operator (>=) | GreaterEqual + /// SQL less than or equal operator (<=) | LessEqual + /// SQL IS NULL operator for checking null values | IsNull + /// SQL IS NOT NULL operator for checking non-null values | NotNull + /// SQL IN operator for membership testing | In + /// SQL NOT IN operator for negated membership testing | NotIn + /// SQL IN operator for nested subqueries | NestedIn + /// SQL NOT IN operator for negated nested subqueries | NestedNotIn + /// SQL EXISTS operator for subquery existence testing | NestedExists + /// SQL NOT EXISTS operator for negated subquery existence testing | NestedNotExists with override x.ToString() = @@ -42,127 +62,273 @@ type ConditionOperator = | NestedExists -> "EXISTS" | NestedNotExists -> "NOT EXISTS" +/// +/// Represents SQL aggregate operations for GROUP BY clauses and data summarization. +/// Each operation includes the column name to aggregate. +/// [] type AggregateOperation = // Aggregate (column name if not default) +/// Groups results by the specified key column | KeyOp of key: string +/// Finds the maximum value in the specified column | MaxOp of max: string +/// Finds the minimum value in the specified column | MinOp of min: string +/// Calculates the sum of values in the specified column | SumOp of sum: string +/// Calculates the average of values in the specified column | AvgOp of avg: string +/// Counts the number of rows in the specified column | CountOp of count: string +/// Counts the number of distinct values in the specified column | CountDistOp of countDist: string +/// Calculates the standard deviation of values in the specified column | StdDevOp of std: string +/// Calculates the variance of values in the specified column | VarianceOp of var: string +/// +/// Specifies where SELECT operations should be executed for query optimization. +/// type SelectOperations = +/// Execute the operation on the .NET client side | DotNetSide = 0 +/// Execute the operation on the database server side | DatabaseSide = 1 [] module ColumnSchema = type alias = string + /// + /// Represents WHERE clause conditions for SQL queries. + /// Supports complex boolean expressions with AND/OR operators and nested conditions. + /// type Condition = // this is (table alias * column name * operator * right hand value ) list * (the same again list) // basically any AND or OR expression can have N terms and can have N nested condition children // this is largely from my CRM type provider. I don't think in practice for the SQL provider // you will ever have more than what is representable in a traditional binary expression tree, but // changing it would be a lot of effort ;) + /// AND condition with a list of terms and optional nested conditions | And of (alias * SqlColumnType * ConditionOperator * obj option) list * (Condition list) option + /// OR condition with a list of terms and optional nested conditions | Or of (alias * SqlColumnType * ConditionOperator * obj option) list * (Condition list) option + /// Always evaluates to true | ConstantTrue + /// Always evaluates to false | ConstantFalse + /// Expression that cannot be translated to SQL | NotSupported of System.Linq.Expressions.Expression + /// + /// Represents SQL functions and operations that can be applied to columns. + /// These operations are translated to database-specific SQL functions. + /// and CanonicalOp = //String functions + /// Extracts a substring from the specified position to the end | Substring of SqlItemOrColumn + /// Extracts a substring with specified start position and length | SubstringWithLength of SqlItemOrColumn*SqlItemOrColumn + /// Converts string to uppercase | ToUpper + /// Converts string to lowercase | ToLower + /// Removes leading and trailing whitespace | Trim + /// Returns the length of a string | Length + /// Replaces occurrences of a substring with another substring | Replace of SqlItemOrColumn*SqlItemOrColumn + /// Returns the index of the first occurrence of a substring | IndexOf of SqlItemOrColumn + /// Returns the index of a substring starting from a specified position | IndexOfStart of SqlItemOrColumn*SqlItemOrColumn // Date functions + /// Extracts the date part from a datetime value | Date + /// Extracts the year from a datetime value | Year + /// Extracts the month from a datetime value | Month + /// Extracts the day from a datetime value | Day + /// Extracts the hour from a datetime value | Hour + /// Extracts the minute from a datetime value | Minute + /// Extracts the second from a datetime value | Second + /// Adds the specified number of years to a datetime value | AddYears of SqlItemOrColumn + /// Adds the specified number of months to a datetime value | AddMonths of int + /// Adds the specified number of days to a datetime value | AddDays of SqlItemOrColumn + /// Adds the specified number of hours to a datetime value | AddHours of float + /// Adds the specified number of minutes to a datetime value | AddMinutes of SqlItemOrColumn + /// Adds the specified number of seconds to a datetime value | AddSeconds of float + /// Calculates the difference in days between two datetime values | DateDiffDays of SqlItemOrColumn + /// Calculates the difference in seconds between two datetime values | DateDiffSecs of SqlItemOrColumn // Numerical functions + /// Returns the absolute value of a number | Abs + /// Returns the smallest integer greater than or equal to a number (ceiling) | Ceil + /// Returns the largest integer less than or equal to a number (floor) | Floor + /// Rounds a number to the nearest integer | Round + /// Rounds a number to the specified number of decimal places | RoundDecimals of int + /// Truncates a number to its integer part | Truncate + /// Returns the square root of a number | Sqrt + /// Returns the sine of an angle (in radians) | Sin + /// Returns the cosine of an angle (in radians) | Cos + /// Returns the tangent of an angle (in radians) | Tan + /// Returns the arcsine of a number | ASin + /// Returns the arccosine of a number | ACos + /// Returns the arctangent of a number | ATan + /// Raises a number to the power of another number | Pow of SqlItemOrColumn + /// Raises a number to a constant power | PowConst of SqlItemOrColumn + /// Returns the greatest value among the provided arguments | Greatest of SqlItemOrColumn + /// Returns the smallest value among the provided arguments | Least of SqlItemOrColumn // Other + /// Basic mathematical operation with a constant value | BasicMath of string*obj //operation, constant + /// Left-side basic mathematical operation with a constant value | BasicMathLeft of string*obj //operation, constant + /// Basic mathematical operation between columns | BasicMathOfColumns of string*string*SqlColumnType //operation, alias, column + /// SQL CASE expression with condition and false value | CaseSql of Condition * SqlItemOrColumn // operation, if-false + /// SQL CASE expression with negated condition and true value | CaseNotSql of Condition * SqlItemOrColumn // operation, if-true + /// SQL CASE expression with condition and two constant values | CaseSqlPlain of Condition * obj * obj // with 2 constants + /// Casts a value to VARCHAR type | CastVarchar + /// Casts a value to INTEGER type | CastInt + /// + /// Represents a column with optional operations applied to it. + /// and SqlColumnType = + /// A key column used for grouping or joining | KeyColumn of string + /// A column with a canonical operation applied | CanonicalOperation of CanonicalOp * SqlColumnType + /// A column used in aggregate operations | GroupColumn of AggregateOperation * SqlColumnType + /// + /// Represents either a database column or a constant value in SQL expressions. + /// // More recursion, because you mighn want to say e.g. // where (x.Substring(x.IndexOf("."), (x.Length-x.IndexOf(".")) and SqlItemOrColumn = + /// A database column with table alias and column type | SqlCol of string*SqlColumnType //alias*column + /// A constant value to be used in SQL expressions | SqlConstant of obj + /// + /// Represents parameters for SQL projections (SELECT clause elements). + /// type ProjectionParameter = + /// A direct entity column reference | EntityColumn of string + /// A column with operations applied for computed expressions | OperationColumn of string*SqlColumnType//name*operations // Dummy operators, these are placeholders that are replaced in the expression tree traversal with special server-side operations such as In, Like // The operators here are used to force the compiler to statically check against the correct types +/// +/// Contains custom SQL operators for use in query expressions. +/// These operators are translated to their SQL equivalents during query compilation. +/// [] module Operators = - //// In + /// + /// SQL IN operator. Tests if a value exists in a sequence. + /// Example: where (customer.Country |=| ["USA"; "Canada"]) + /// + /// The value to test + /// The sequence to search in + /// Always false at compile time - replaced with SQL IN during query execution let (|=|) (a:'a) (b:'a seq) = false - /// Not In + + /// + /// SQL NOT IN operator. Tests if a value does not exist in a sequence. + /// Example: where (customer.Country |<>| ["USA"; "Canada"]) + /// + /// The value to test + /// The sequence to search in + /// Always false at compile time - replaced with SQL NOT IN during query execution let (|<>|) (a:'a) (b:'a seq) = false - /// Like + + /// + /// SQL LIKE operator for pattern matching with wildcards. + /// Example: where (customer.Name =% "John%") + /// + /// The value to test + /// The pattern to match (use % and _ as wildcards) + /// Always false at compile time - replaced with SQL LIKE during query execution let (=%) (a:'a) (b:string) = false - /// Not Like + + /// + /// SQL NOT LIKE operator for negated pattern matching. + /// Example: where (customer.Name <>% "John%") + /// + /// The value to test + /// The pattern to reject + /// Always false at compile time - replaced with SQL NOT LIKE during query execution let (<>%) (a:'a) (b:string) = false - /// Left join + + /// Left join helper function for internal use let private leftJoin (a:'a) = a + + /// + /// Left outer join operator. Performs a left join on a related table. + /// Example: for customer in ctx.Main.Customers do + /// for order in (!!) customer.``main.Orders by CustomerID`` do + /// + /// The related table queryable + /// A queryable that performs a left outer join let (!!) (a:IQueryable<'a>) = query { for x in a do select (leftJoin x) } - /// Standard Deviation + /// + /// Calculates the standard deviation of numeric values in a column. + /// Used in aggregate queries with groupBy. + /// + /// The column value + /// Always 1m at compile time - replaced with SQL STDDEV during query execution let StdDev (a:'a) = 1m - /// Variance + /// + /// Calculates the variance of numeric values in a column. + /// Used in aggregate queries with groupBy. + /// + /// The column value + /// Always 1m at compile time - replaced with SQL VARIANCE during query execution let Variance (a:'a) = 1m diff --git a/src/SQLProvider.Common/SqlRuntime.Async.fs b/src/SQLProvider.Common/SqlRuntime.Async.fs index 9c8db3e3..3abf82cd 100644 --- a/src/SQLProvider.Common/SqlRuntime.Async.fs +++ b/src/SQLProvider.Common/SqlRuntime.Async.fs @@ -8,8 +8,18 @@ open FSharp.Data.Sql.Common open FSharp.Data.Sql.Patterns open System.Linq +/// +/// Provides asynchronous operations for SQL queries. +/// Use these functions when you need to execute queries asynchronously to avoid blocking threads. +/// module AsyncOperations = + /// + /// Executes a queryable asynchronously and returns the results as a sequence. + /// This is useful for executing large queries without blocking the calling thread. + /// + /// The queryable to execute + /// A task that yields a sequence of results let executeAsync (s:Linq.IQueryable<'T>) = let inline yieldseq (en: IEnumerator<'T>) = seq { diff --git a/src/SQLProvider.Common/SqlRuntime.Common.fs b/src/SQLProvider.Common/SqlRuntime.Common.fs index 1b80d0f3..76b8492c 100644 --- a/src/SQLProvider.Common/SqlRuntime.Common.fs +++ b/src/SQLProvider.Common/SqlRuntime.Common.fs @@ -16,27 +16,57 @@ open Microsoft.FSharp.Reflection open System.Collections.Concurrent open System.Runtime.Serialization +/// +/// Specifies the database provider type for the SQL type provider. +/// Each provider has its own specific implementation for SQL generation and data type mapping. +/// type DatabaseProviderTypes = + /// Microsoft SQL Server using SqlClient provider | MSSQLSERVER = 0 + /// SQLite database using System.Data.SQLite or Microsoft.Data.Sqlite | SQLITE = 1 + /// PostgreSQL using Npgsql provider | POSTGRESQL = 2 + /// MySQL using MySql.Data or MySqlConnector provider | MYSQL = 3 + /// Oracle Database using Oracle.ManagedDataAccess provider | ORACLE = 4 + /// Microsoft Access using OleDb provider | MSACCESS = 5 + /// Generic ODBC provider for various databases | ODBC = 6 + /// Firebird database using FirebirdSql.Data.FirebirdClient | FIREBIRD = 7 + /// Microsoft SQL Server with dynamic schema loading | MSSQLSERVER_DYNAMIC = 8 + /// Microsoft SQL Server using SSDT/DacPac for schema | MSSQLSERVER_SSDT = 9 + /// DuckDB using DuckDB.NET provider | DUCKDB = 10 + /// External/custom database provider | EXTERNAL = 11 -type RelationshipDirection = Children = 0 | Parents = 1 +/// Specifies the direction for relationship navigation. +type RelationshipDirection = + /// Navigate to child records (foreign key relationships) + Children = 0 + /// Navigate to parent records (primary key relationships) + | Parents = 1 +/// +/// Specifies how to handle case sensitivity when generating table and column names. +/// type CaseSensitivityChange = + /// Keep original casing from the database | ORIGINAL = 0 + /// Convert all names to uppercase | TOUPPER = 1 + /// Convert all names to lowercase | TOLOWER = 2 +/// +/// Specifies how to represent nullable database columns in the generated types. +/// type NullableColumnType = /// Nullable types are just Unchecked default. (Old false.) | NO_OPTION = 0 @@ -45,7 +75,11 @@ type NullableColumnType = /// ValueOption is more performant. | VALUE_OPTION = 2 +/// +/// Specifies the quote character style for ODBC connections when dealing with table and column names containing spaces or special characters. +/// type OdbcQuoteCharacter = + /// Use default quoting for the ODBC driver | DEFAULT_QUOTE = 0 /// MySQL/Postgre style: `alias` | GRAVE_ACCENT = 1 @@ -58,6 +92,10 @@ type OdbcQuoteCharacter = /// Single quote: 'alias' | APHOSTROPHE = 5 +/// +/// Specifies which SQLite library to use for connections. +/// Different libraries may have different capabilities and platform support. +/// type SQLiteLibrary = /// .NET Framework default | SystemDataSQLite = 0 @@ -68,8 +106,16 @@ type SQLiteLibrary = /// Microsoft.Data.Sqlite. May support .NET Standard 2.0 contract in the future. | MicrosoftDataSqlite = 3 +/// +/// Contains events for monitoring SQL query execution and debugging. +/// Use these events to log, debug, or analyze the SQL queries generated by the type provider. +/// module public QueryEvents = + /// + /// Contains information about a SQL command being executed, including the command text, + /// parameters, and connection information for debugging and monitoring purposes. + /// type SqlEventData = { /// The text of the SQL command being executed. Command: string @@ -112,8 +158,18 @@ module public QueryEvents = let private sqlEvent = Event() - /// This event fires immediately before the execution of every generated query. + /// + /// Event that fires immediately before the execution of every generated query. /// Listen to this event to display or debug the content of your queries. + /// This is useful for logging, performance monitoring, and debugging SQL generation. + /// + /// + /// + /// QueryEvents.SqlQueryEvent.Add(fun event -> + /// printfn "Executing: %s" event.Command + /// printfn "Parameters: %A" event.Parameters) + /// + /// [] let SqlQueryEvent = sqlEvent.Publish @@ -150,13 +206,26 @@ module public QueryEvents = let internal PublishExpression(e) = expressionEvent.Trigger(e) [] +/// +/// Represents the current state of a database entity for change tracking purposes. +/// Used internally by the SQL provider to determine what operations need to be performed during SubmitUpdates(). +/// type EntityState = + /// Entity has not been modified since it was loaded | Unchanged + /// Entity is new and should be inserted into the database | Created + /// Entity has been modified; contains list of changed column names | Modified of string list + /// Entity is marked for deletion (soft delete) | Delete + /// Entity has been deleted from the database | Deleted +/// +/// Specifies how to handle conflicts when inserting records with duplicate primary keys. +/// Currently supported only on databases that have UPSERT capabilities. +/// [] type OnConflict = /// Throws an exception if the primary key already exists (default behaviour). @@ -168,18 +237,40 @@ type OnConflict = /// Currently supported only on PostgreSQL 9.5+ | DoNothing +/// +/// Attribute for mapping entity properties to database columns with different names. +/// Use this when your database column name differs from your preferred F# property name. +/// +/// +/// +/// [<MappedColumn("customer_id")>] +/// member x.CustomerId = x.GetColumn<int>("customer_id") +/// +/// type MappedColumnAttribute(name: string) = inherit Attribute() + /// Gets the database column name this property maps to member x.Name with get() = name +/// Represents a result set as a sequence of rows, where each row is an array of column name-value pairs. type ResultSet = seq<(string * obj)[]> + +/// Represents different types of result sets that can be returned from stored procedures. type ReturnSetType = + /// A single scalar value with name and value | ScalarResultSet of string * obj + /// A result set with name and data rows | ResultSet of string * ResultSet + +/// Represents different types of return values from stored procedures and functions. type ReturnValueType = + /// No return value (void) | Unit + /// A single scalar value | Scalar of string * obj + /// A single result set | SingleResultSet of string * ResultSet + /// Multiple result sets | Set of seq /// Interface to hide internal members that are typically not needed by the user @@ -478,6 +569,11 @@ type SqlEntity(dc: ISqlDataContext, tableName, columns: ColumnLookup, activeColu override __.ShouldSerializeValue(_) = false }) |> Seq.cast |> Seq.toArray ) +/// +/// The main interface for SQL data context operations. This interface provides all the core functionality +/// for querying, creating, updating, and deleting data through the SQL type provider. +/// Users typically access this through the generated GetDataContext() method. +/// and ISqlDataContext = /// Connection string to database abstract ConnectionString : string @@ -520,8 +616,12 @@ and ISqlDataContext = /// Context meant to be read operations only abstract IsReadOnly : bool -/// This is publically exposed and used in the runtime +/// +/// Interface implemented by generated types that provide access to the underlying data context. +/// This allows access to the ISqlDataContext for advanced operations. +/// type IWithDataContext = + /// Gets the underlying SQL data context abstract DataContext : ISqlDataContext /// LinkData is for joins with SelectMany diff --git a/src/SQLProvider.Common/SqlRuntime.Transactions.fs b/src/SQLProvider.Common/SqlRuntime.Transactions.fs index 0de238dc..a0eebd82 100644 --- a/src/SQLProvider.Common/SqlRuntime.Transactions.fs +++ b/src/SQLProvider.Common/SqlRuntime.Transactions.fs @@ -2,24 +2,43 @@ namespace FSharp.Data.Sql.Transactions open System -/// Corresponds to the System.Transactions.IsolationLevel. +/// +/// Transaction isolation level for database operations. +/// Corresponds to the System.Transactions.IsolationLevel but provides SQL provider specific functionality. +/// type IsolationLevel = + /// Highest isolation level - transactions are completely isolated from each other | Serializable = 0 + /// Prevents dirty reads and non-repeatable reads, but phantom reads can occur | RepeatableRead = 1 + /// Prevents dirty reads but allows non-repeatable reads and phantom reads (default for most databases) | ReadCommitted = 2 + /// Allows dirty reads, non-repeatable reads, and phantom reads (fastest but least safe) | ReadUncommitted = 3 + /// SQL Server specific - provides statement-level read consistency using versioning | Snapshot = 4 + /// Allows any level of isolation, including overlapping changes (SQL Server specific) | Chaos = 5 + /// Use the default isolation level of the database | Unspecified = 6 + /// Special value to indicate that no transaction should be created | DontCreateTransaction = 99 +/// +/// Configuration options for database transactions. /// Corresponds to the System.Transactions.TransactionOptions. +/// [] type TransactionOptions = { + /// Maximum time the transaction can remain active before timing out Timeout : TimeSpan + /// The isolation level for the transaction IsolationLevel : IsolationLevel } +/// +/// Utility functions for converting between different transaction isolation level representations. +/// module TransactionUtils = let internal toSystemTransactionsIsolationLevel isolationLevel = match isolationLevel with @@ -32,6 +51,12 @@ module TransactionUtils = | IsolationLevel.Unspecified -> System.Transactions.IsolationLevel.Unspecified | _ -> failwithf "Unhandled IsolationLevel value: %A." isolationLevel + /// + /// Converts SQL provider isolation level to System.Data.IsolationLevel. + /// Use this when you need to work directly with ADO.NET connection transactions. + /// + /// The SQL provider isolation level + /// The corresponding System.Data.IsolationLevel let toSystemDataIsolationLevel isolationLevel = match isolationLevel with | IsolationLevel.Serializable -> System.Data.IsolationLevel.Serializable diff --git a/src/SQLProvider.Common/SqlSchema.fs b/src/SQLProvider.Common/SqlSchema.fs index 2f1812a0..a322d1c2 100644 --- a/src/SQLProvider.Common/SqlSchema.fs +++ b/src/SQLProvider.Common/SqlSchema.fs @@ -17,11 +17,19 @@ module internal Patterns = | _ -> ValueNone else ValueNone +/// +/// Represents the mapping between database types and .NET CLR types. +/// Used to translate between database-specific types and .NET types during query execution. +/// [] type TypeMapping = + /// The database provider's specific type name (e.g., "varchar", "int") { ProviderTypeName: string voption + /// The corresponding .NET CLR type name ClrType: string + /// The database provider's numeric type identifier ProviderType: int voption + /// The standard .NET DbType enumeration value DbType: DbType } with static member Create(?clrType, ?dbType, ?providerTypeName, ?providerType) = @@ -30,12 +38,21 @@ type TypeMapping = ProviderTypeName = match providerTypeName with Some x -> ValueSome x | None -> ValueNone ProviderType = match providerType with Some x -> ValueSome x | None -> ValueNone } +/// +/// Represents a parameter used in SQL queries, stored procedures, or functions. +/// Contains all metadata needed to properly bind .NET values to database parameters. +/// [] type QueryParameter = + /// The parameter name as used in the SQL query { Name: string + /// Type mapping information for converting between .NET and database types TypeMapping: TypeMapping + /// Whether the parameter is input, output, or bidirectional Direction: ParameterDirection + /// Maximum length for string/binary parameters Length: int voption + /// Position of the parameter in the parameter list (zero-based) Ordinal: int } with static member Create(name, ordinal, ?typeMapping, ?direction, ?length) = @@ -45,15 +62,27 @@ type QueryParameter = Direction = defaultArg direction ParameterDirection.Input Length = match length with Some x -> ValueSome x | None -> ValueNone } +/// +/// Represents a database table column with its metadata and characteristics. +/// Contains all information needed to generate typed properties and handle data conversion. +/// [] type Column = + /// The column name as it appears in the database { Name: string + /// Type mapping information for converting between .NET and database types TypeMapping: TypeMapping + /// True if this column is part of the table's primary key IsPrimaryKey: bool + /// True if this column can contain NULL values IsNullable: bool + /// True if this column has an auto-increment/identity specification IsAutonumber: bool + /// True if this column has a default value defined HasDefault: bool + /// True if this column is computed/calculated by the database IsComputed: bool + /// Additional type information specific to the database provider TypeInfo: string voption } with static member FromQueryParameter(q: QueryParameter) = @@ -66,19 +95,36 @@ type Column = IsComputed = false TypeInfo = ValueNone } +/// Lookup table for quickly finding column information by name. type ColumnLookup = Map +/// +/// Represents a foreign key relationship between two database tables. +/// Used for automatic navigation property generation and join optimization. +/// [] type Relationship = + /// A descriptive name for this relationship { Name: string + /// The name of the table containing the primary key PrimaryTable: string + /// The column name of the primary key PrimaryKey: string + /// The name of the table containing the foreign key ForeignTable: string + /// The column name of the foreign key ForeignKey: string } +/// +/// Represents the name of a stored procedure, including schema and package information. +/// Handles different database naming conventions (e.g., Oracle packages, SQL Server schemas). +/// type SprocName = + /// The procedure name { ProcName: string + /// The schema or owner name Owner: string + /// The package name (primarily for Oracle) PackageName: string } with member x.ToList() = @@ -117,9 +163,16 @@ type Sproc = typedefof.GetNestedTypes(BindingFlags.Public ||| BindingFlags.NonPublic) |> Array.filter Microsoft.FSharp.Reflection.FSharpType.IsUnion +/// +/// Represents a database table with its schema information. +/// Provides methods for generating properly quoted table names for different database providers. +/// type Table = + /// The schema name (may be empty for databases that don't use schemas) { Schema: string + /// The table name Name: string + /// The table type (e.g., "TABLE", "VIEW", "SYSTEM TABLE") Type: string } with static member CreateQuotedFullName(schema, name, startQuote, endQuote) = diff --git a/src/SQLProvider.Common/Ssdt.DacpacParser.fs b/src/SQLProvider.Common/Ssdt.DacpacParser.fs index 12dccc5c..066f4468 100644 --- a/src/SQLProvider.Common/Ssdt.DacpacParser.fs +++ b/src/SQLProvider.Common/Ssdt.DacpacParser.fs @@ -1,50 +1,94 @@ +/// +/// Module for parsing SQL Server Data Tools (SSDT) .dacpac files to extract database schema information. +/// Provides functionality to read and parse the model.xml file contained within .dacpac packages. +/// module FSharp.Data.Sql.Ssdt.DacpacParser open System open System.Xml open System.IO.Compression +/// Represents a database table column from SSDT schema. type [] SsdtColumn = { + /// The fully qualified column name (schema.table.column) FullName: string + /// The column name without qualification Name: string + /// Extended description/comment for the column Description: string + /// The SQL data type (e.g., varchar, int, datetime) DataType: string + /// True if the column allows NULL values AllowNulls: bool + /// True if the column is an identity/auto-increment column IsIdentity: bool + /// True if the column has a default value constraint HasDefault: bool + /// True if the column is computed/calculated ComputedColumn: bool } + +/// Represents a view column from SSDT schema. type [] SsdtViewColumn = { + /// The fully qualified column name FullName: string + /// Optional reference path for complex column definitions ColumnRefPath: string voption } + +/// Represents a comment annotation for schema documentation. type [] CommentAnnotation = { + /// The column name the annotation applies to Column: string + /// The data type information DataType: string + /// Optional nullability information Nullability: string voption } + +/// Represents a database view from SSDT schema. type SsdtView = { + /// The fully qualified view name (schema.view) FullName: string + /// The schema name Schema: string + /// The view name without schema Name: string + /// Array of regular columns in the view Columns: SsdtViewColumn array + /// Array of dynamically generated columns DynamicColumns: SsdtViewColumn array + /// Array of comment annotations for documentation Annotations: CommentAnnotation array } + +/// Represents a column in a constraint definition. type [] ConstraintColumn = { + /// The fully qualified column name FullName: string + /// The column name without qualification Name: string } + +/// Represents a table reference in relationship definitions. type RefTable = { + /// The fully qualified table name FullName: string + /// The schema name Schema: string + /// The table name without schema Name: string + /// Array of columns involved in the reference Columns: ConstraintColumn array } +/// Represents a foreign key relationship between tables. type SsdtRelationship = { + /// The name of the relationship/constraint Name: string + /// The table that defines the primary key DefiningTable: RefTable + /// The table that contains the foreign key ForeignTable: RefTable } @@ -138,7 +182,12 @@ module RegexParsers = ) |> Seq.toArray -/// Extracts model.xml from the given .dacpac file path. +/// +/// Extracts the model.xml file from a SQL Server Data Tools (SSDT) .dacpac package. +/// The model.xml contains the complete database schema definition. +/// +/// Path to the .dacpac file +/// The XML content of the model.xml file as a string let extractModelXml (dacPacPath: string) = use stream = new IO.FileStream(dacPacPath, IO.FileMode.Open, IO.FileAccess.Read) use zip = new ZipArchive(stream, ZipArchiveMode.Read, false) @@ -147,7 +196,13 @@ let extractModelXml (dacPacPath: string) = use rdr = new IO.StreamReader(modelStream) rdr.ReadToEnd() -/// Returns a doc and node/nodes ns helper fns +/// +/// Creates an XML document with namespace support and returns helper functions for node selection. +/// This is specifically designed for parsing SSDT model.xml files with their Microsoft namespace. +/// +/// The XML namespace URI +/// The XML content to parse +/// A tuple containing (XmlDocument, single node selector function, multiple nodes selector function) let toXmlNamespaceDoc ns xml = let doc = XmlDocument() let nsMgr = XmlNamespaceManager(doc.NameTable) @@ -162,6 +217,12 @@ let toXmlNamespaceDoc ns xml = doc, node, nodes +/// +/// Safely extracts an XML attribute value from a node, returning None if the attribute doesn't exist. +/// +/// The attribute name to extract +/// The XML node to extract from +/// Some(attribute value) if found, None otherwise let attMaybe (nm: string) (node: XmlNode) = if isNull node.Attributes then None else @@ -170,10 +231,22 @@ let attMaybe (nm: string) (node: XmlNode) = |> Seq.tryFind (fun a -> a.Name = nm) |> Option.map (fun a -> a.Value) +/// +/// Extracts an XML attribute value from a node, returning an empty string if the attribute doesn't exist. +/// This is a convenience function for cases where a default empty value is acceptable. +/// +/// The attribute name to extract +/// The XML node to extract from +/// The attribute value, or empty string if not found let att (nm: string) (node: XmlNode) = attMaybe nm node |> Option.defaultValue "" -/// Parses the xml that is extracted from a .dacpac file. +/// +/// Parses the model.xml content extracted from a SQL Server Data Tools (SSDT) .dacpac file. +/// Extracts database schema information including tables, views, columns, relationships, and stored procedures. +/// +/// The XML content from the model.xml file +/// A parsed representation of the database schema let parseXml(xml: string) = let removeBrackets (s: string) = s.Replace("[", "").Replace("]", "")