diff --git a/ast/alter_database_set_statement.go b/ast/alter_database_set_statement.go index 0b1bb23d..b1af86e6 100644 --- a/ast/alter_database_set_statement.go +++ b/ast/alter_database_set_statement.go @@ -6,8 +6,18 @@ type AlterDatabaseSetStatement struct { UseCurrent bool WithManualCutover bool Options []DatabaseOption + Termination *AlterDatabaseTermination } +// AlterDatabaseTermination represents the termination clause (WITH NO_WAIT, WITH ROLLBACK AFTER N, WITH ROLLBACK IMMEDIATE) +type AlterDatabaseTermination struct { + NoWait bool + ImmediateRollback bool + RollbackAfter ScalarExpression +} + +func (a *AlterDatabaseTermination) node() {} + func (a *AlterDatabaseSetStatement) node() {} func (a *AlterDatabaseSetStatement) statement() {} @@ -82,6 +92,7 @@ type SimpleDatabaseOption struct { func (d *SimpleDatabaseOption) node() {} func (d *SimpleDatabaseOption) createDatabaseOption() {} +func (d *SimpleDatabaseOption) databaseOption() {} // MaxSizeDatabaseOption represents a MAXSIZE option. type MaxSizeDatabaseOption struct { @@ -104,6 +115,16 @@ func (l *LiteralDatabaseOption) node() {} func (l *LiteralDatabaseOption) databaseOption() {} func (l *LiteralDatabaseOption) createDatabaseOption() {} +// ElasticPoolSpecification represents SERVICE_OBJECTIVE = ELASTIC_POOL(name = poolname) +type ElasticPoolSpecification struct { + ElasticPoolName *Identifier + OptionKind string // "ServiceObjective" +} + +func (e *ElasticPoolSpecification) node() {} +func (e *ElasticPoolSpecification) databaseOption() {} +func (e *ElasticPoolSpecification) createDatabaseOption() {} + // AlterDatabaseAddFileStatement represents ALTER DATABASE ... ADD FILE statement type AlterDatabaseAddFileStatement struct { DatabaseName *Identifier @@ -145,11 +166,23 @@ type AlterDatabaseModifyFileGroupStatement struct { MakeDefault bool UpdatabilityOption string // "ReadOnly", "ReadWrite", "ReadOnlyOld", "ReadWriteOld", or "" NewFileGroupName *Identifier + Termination *AlterDatabaseTermination + UseCurrent bool } func (a *AlterDatabaseModifyFileGroupStatement) node() {} func (a *AlterDatabaseModifyFileGroupStatement) statement() {} +// AlterDatabaseRebuildLogStatement represents ALTER DATABASE ... REBUILD LOG statement +type AlterDatabaseRebuildLogStatement struct { + DatabaseName *Identifier + FileDeclaration *FileDeclaration + UseCurrent bool +} + +func (a *AlterDatabaseRebuildLogStatement) node() {} +func (a *AlterDatabaseRebuildLogStatement) statement() {} + // AlterDatabaseModifyNameStatement represents ALTER DATABASE ... MODIFY NAME statement type AlterDatabaseModifyNameStatement struct { DatabaseName *Identifier @@ -279,3 +312,68 @@ type ChangeRetentionChangeTrackingOptionDetail struct { func (c *ChangeRetentionChangeTrackingOptionDetail) node() {} func (c *ChangeRetentionChangeTrackingOptionDetail) changeTrackingOptionDetail() {} + +// RecoveryDatabaseOption represents RECOVERY database option +type RecoveryDatabaseOption struct { + OptionKind string // "Recovery" + Value string // "Full", "BulkLogged", "Simple" +} + +func (r *RecoveryDatabaseOption) node() {} +func (r *RecoveryDatabaseOption) databaseOption() {} + +// CursorDefaultDatabaseOption represents CURSOR_DEFAULT database option +type CursorDefaultDatabaseOption struct { + OptionKind string // "CursorDefault" + IsLocal bool // true for LOCAL, false for GLOBAL +} + +func (c *CursorDefaultDatabaseOption) node() {} +func (c *CursorDefaultDatabaseOption) databaseOption() {} + +// PageVerifyDatabaseOption represents PAGE_VERIFY database option +type PageVerifyDatabaseOption struct { + OptionKind string // "PageVerify" + Value string // "Checksum", "None", "TornPageDetection" +} + +func (p *PageVerifyDatabaseOption) node() {} +func (p *PageVerifyDatabaseOption) databaseOption() {} + +// PartnerDatabaseOption represents PARTNER database mirroring option +type PartnerDatabaseOption struct { + OptionKind string // "Partner" + PartnerServer ScalarExpression // For PARTNER = 'server' + PartnerOption string // "PartnerServer", "Failover", "ForceServiceAllowDataLoss", "Resume", "SafetyFull", "SafetyOff", "Suspend", "Timeout" + Timeout ScalarExpression // For PARTNER TIMEOUT value +} + +func (p *PartnerDatabaseOption) node() {} +func (p *PartnerDatabaseOption) databaseOption() {} + +// WitnessDatabaseOption represents WITNESS database mirroring option +type WitnessDatabaseOption struct { + OptionKind string // "Witness" + WitnessServer ScalarExpression // For WITNESS = 'server' + IsOff bool // For WITNESS OFF +} + +func (w *WitnessDatabaseOption) node() {} +func (w *WitnessDatabaseOption) databaseOption() {} + +// ParameterizationDatabaseOption represents PARAMETERIZATION database option +type ParameterizationDatabaseOption struct { + OptionKind string // "Parameterization" + IsSimple bool // true for SIMPLE, false for FORCED +} + +func (p *ParameterizationDatabaseOption) node() {} +func (p *ParameterizationDatabaseOption) databaseOption() {} + +// GenericDatabaseOption represents a simple database option with just OptionKind +type GenericDatabaseOption struct { + OptionKind string // e.g., "Emergency", "ErrorBrokerConversations", "EnableBroker", etc. +} + +func (g *GenericDatabaseOption) node() {} +func (g *GenericDatabaseOption) databaseOption() {} diff --git a/ast/alter_procedure_statement.go b/ast/alter_procedure_statement.go index 9006cfb6..090805a2 100644 --- a/ast/alter_procedure_statement.go +++ b/ast/alter_procedure_statement.go @@ -4,6 +4,7 @@ package ast type AlterProcedureStatement struct { ProcedureReference *ProcedureReference Parameters []*ProcedureParameter + Options []ProcedureOptionBase StatementList *StatementList IsForReplication bool } diff --git a/ast/alter_table_alter_column_statement.go b/ast/alter_table_alter_column_statement.go index 85f1f5bd..acc9bc68 100644 --- a/ast/alter_table_alter_column_statement.go +++ b/ast/alter_table_alter_column_statement.go @@ -5,10 +5,13 @@ type AlterTableAlterColumnStatement struct { SchemaObjectName *SchemaObjectName ColumnIdentifier *Identifier DataType DataTypeReference - AlterTableAlterColumnOption string // "NoOptionDefined", "AddRowGuidCol", "DropRowGuidCol", "Null", "NotNull", etc. + AlterTableAlterColumnOption string // "NoOptionDefined", "AddRowGuidCol", "DropRowGuidCol", "Null", "NotNull", "AddSparse", "DropSparse", etc. + StorageOptions *ColumnStorageOptions IsHidden bool Collation *Identifier IsMasked bool + Encryption *ColumnEncryptionDefinition + MaskingFunction ScalarExpression } func (a *AlterTableAlterColumnStatement) node() {} diff --git a/ast/alter_table_change_tracking_statement.go b/ast/alter_table_change_tracking_statement.go new file mode 100644 index 00000000..064a7854 --- /dev/null +++ b/ast/alter_table_change_tracking_statement.go @@ -0,0 +1,11 @@ +package ast + +// AlterTableChangeTrackingModificationStatement represents ALTER TABLE ... ENABLE/DISABLE CHANGE_TRACKING +type AlterTableChangeTrackingModificationStatement struct { + SchemaObjectName *SchemaObjectName + IsEnable bool // true for ENABLE, false for DISABLE + TrackColumnsUpdated string // "NotSet", "On", "Off" +} + +func (s *AlterTableChangeTrackingModificationStatement) node() {} +func (s *AlterTableChangeTrackingModificationStatement) statement() {} diff --git a/ast/alter_table_rebuild_statement.go b/ast/alter_table_rebuild_statement.go new file mode 100644 index 00000000..aaddd545 --- /dev/null +++ b/ast/alter_table_rebuild_statement.go @@ -0,0 +1,11 @@ +package ast + +// AlterTableRebuildStatement represents ALTER TABLE ... REBUILD statement +type AlterTableRebuildStatement struct { + SchemaObjectName *SchemaObjectName + Partition *PartitionSpecifier + IndexOptions []IndexOption +} + +func (s *AlterTableRebuildStatement) node() {} +func (s *AlterTableRebuildStatement) statement() {} diff --git a/ast/alter_table_set_statement.go b/ast/alter_table_set_statement.go index 9d45962e..fef34987 100644 --- a/ast/alter_table_set_statement.go +++ b/ast/alter_table_set_statement.go @@ -44,3 +44,30 @@ type MemoryOptimizedTableOption struct { func (o *MemoryOptimizedTableOption) tableOption() {} func (o *MemoryOptimizedTableOption) node() {} + +// DurabilityTableOption represents a DURABILITY table option +type DurabilityTableOption struct { + OptionKind string // "Durability" + DurabilityTableOptionKind string // "SchemaOnly", "SchemaAndData" +} + +func (o *DurabilityTableOption) tableOption() {} +func (o *DurabilityTableOption) node() {} + +// LockEscalationTableOption represents LOCK_ESCALATION option +type LockEscalationTableOption struct { + OptionKind string // "LockEscalation" + Value string // "Auto", "Table", "Disable" +} + +func (o *LockEscalationTableOption) tableOption() {} +func (o *LockEscalationTableOption) node() {} + +// FileStreamOnTableOption represents FILESTREAM_ON option +type FileStreamOnTableOption struct { + OptionKind string // "FileStreamOn" + Value *IdentifierOrValueExpression +} + +func (o *FileStreamOnTableOption) tableOption() {} +func (o *FileStreamOnTableOption) node() {} diff --git a/ast/backup_statement.go b/ast/backup_statement.go index b077bf8b..f84884da 100644 --- a/ast/backup_statement.go +++ b/ast/backup_statement.go @@ -6,7 +6,7 @@ type BackupDatabaseStatement struct { DatabaseName *IdentifierOrValueExpression MirrorToClauses []*MirrorToClause Devices []*DeviceInfo - Options []*BackupOption + Options []BackupOptionBase } // MirrorToClause represents a MIRROR TO clause in a BACKUP statement @@ -22,19 +22,37 @@ func (s *BackupDatabaseStatement) node() {} type BackupTransactionLogStatement struct { DatabaseName *IdentifierOrValueExpression Devices []*DeviceInfo - Options []*BackupOption + Options []BackupOptionBase } func (s *BackupTransactionLogStatement) statementNode() {} func (s *BackupTransactionLogStatement) statement() {} func (s *BackupTransactionLogStatement) node() {} +// BackupOptionBase is an interface for backup options +type BackupOptionBase interface { + backupOption() +} + // BackupOption represents a backup option type BackupOption struct { OptionKind string // Compression, NoCompression, StopOnError, ContinueAfterError, etc. Value ScalarExpression } +func (o *BackupOption) backupOption() {} + +// BackupEncryptionOption represents an ENCRYPTION(...) backup option +type BackupEncryptionOption struct { + Algorithm string // Aes128, Aes192, Aes256, TripleDes3Key + Encryptor *CryptoMechanism + OptionKind string // typically "None" +} + +func (o *BackupEncryptionOption) backupOption() {} + +// CryptoMechanism is defined in create_simple_statements.go + // BackupCertificateStatement represents a BACKUP CERTIFICATE statement type BackupCertificateStatement struct { Name *Identifier diff --git a/ast/begin_end_block_statement.go b/ast/begin_end_block_statement.go index cf5f43c6..43a17372 100644 --- a/ast/begin_end_block_statement.go +++ b/ast/begin_end_block_statement.go @@ -38,6 +38,14 @@ type LiteralAtomicBlockOption struct { func (o *LiteralAtomicBlockOption) atomicBlockOption() {} +// OnOffAtomicBlockOption represents an atomic block option with an ON/OFF value. +type OnOffAtomicBlockOption struct { + OptionKind string + OptionState string // "On" or "Off" +} + +func (o *OnOffAtomicBlockOption) atomicBlockOption() {} + // StatementList is a list of statements. type StatementList struct { Statements []Statement `json:"Statements,omitempty"` diff --git a/ast/column_encryption.go b/ast/column_encryption.go new file mode 100644 index 00000000..084f892f --- /dev/null +++ b/ast/column_encryption.go @@ -0,0 +1,37 @@ +package ast + +// ColumnEncryptionDefinition represents the ENCRYPTED WITH specification +type ColumnEncryptionDefinition struct { + Parameters []ColumnEncryptionParameter +} + +func (c *ColumnEncryptionDefinition) node() {} + +// ColumnEncryptionParameter is an interface for encryption parameters +type ColumnEncryptionParameter interface { + columnEncryptionParameter() +} + +// ColumnEncryptionKeyNameParameter represents COLUMN_ENCRYPTION_KEY = key_name +type ColumnEncryptionKeyNameParameter struct { + Name *Identifier + ParameterKind string // "ColumnEncryptionKey" +} + +func (c *ColumnEncryptionKeyNameParameter) columnEncryptionParameter() {} + +// ColumnEncryptionTypeParameter represents ENCRYPTION_TYPE = DETERMINISTIC|RANDOMIZED +type ColumnEncryptionTypeParameter struct { + EncryptionType string // "Deterministic", "Randomized" + ParameterKind string // "EncryptionType" +} + +func (c *ColumnEncryptionTypeParameter) columnEncryptionParameter() {} + +// ColumnEncryptionAlgorithmParameter represents ALGORITHM = 'algorithm_name' +type ColumnEncryptionAlgorithmParameter struct { + EncryptionAlgorithm ScalarExpression // StringLiteral + ParameterKind string // "Algorithm" +} + +func (c *ColumnEncryptionAlgorithmParameter) columnEncryptionParameter() {} diff --git a/ast/create_availability_group_statement.go b/ast/create_availability_group_statement.go new file mode 100644 index 00000000..9ea560a1 --- /dev/null +++ b/ast/create_availability_group_statement.go @@ -0,0 +1,86 @@ +package ast + +// CreateAvailabilityGroupStatement represents a CREATE AVAILABILITY GROUP statement +type CreateAvailabilityGroupStatement struct { + Name *Identifier + Options []AvailabilityGroupOption + Databases []*Identifier + Replicas []*AvailabilityReplica +} + +func (s *CreateAvailabilityGroupStatement) node() {} +func (s *CreateAvailabilityGroupStatement) statement() {} + +// AvailabilityGroupOption is an interface for availability group options +type AvailabilityGroupOption interface { + node() + availabilityGroupOption() +} + +// LiteralAvailabilityGroupOption represents an availability group option with a literal value +type LiteralAvailabilityGroupOption struct { + OptionKind string // e.g., "RequiredCopiesToCommit" + Value ScalarExpression // The value for the option +} + +func (o *LiteralAvailabilityGroupOption) node() {} +func (o *LiteralAvailabilityGroupOption) availabilityGroupOption() {} + +// AvailabilityReplica represents a replica in an availability group +type AvailabilityReplica struct { + ServerName *StringLiteral + Options []AvailabilityReplicaOption +} + +func (r *AvailabilityReplica) node() {} + +// AvailabilityReplicaOption is an interface for availability replica options +type AvailabilityReplicaOption interface { + node() + availabilityReplicaOption() +} + +// AvailabilityModeReplicaOption represents AVAILABILITY_MODE option +type AvailabilityModeReplicaOption struct { + OptionKind string // "AvailabilityMode" + Value string // "SynchronousCommit", "AsynchronousCommit" +} + +func (o *AvailabilityModeReplicaOption) node() {} +func (o *AvailabilityModeReplicaOption) availabilityReplicaOption() {} + +// FailoverModeReplicaOption represents FAILOVER_MODE option +type FailoverModeReplicaOption struct { + OptionKind string // "FailoverMode" + Value string // "Automatic", "Manual" +} + +func (o *FailoverModeReplicaOption) node() {} +func (o *FailoverModeReplicaOption) availabilityReplicaOption() {} + +// LiteralReplicaOption represents a replica option with a literal value +type LiteralReplicaOption struct { + OptionKind string // e.g., "EndpointUrl", "SessionTimeout", "ApplyDelay" + Value ScalarExpression // The value for the option +} + +func (o *LiteralReplicaOption) node() {} +func (o *LiteralReplicaOption) availabilityReplicaOption() {} + +// PrimaryRoleReplicaOption represents PRIMARY_ROLE option +type PrimaryRoleReplicaOption struct { + OptionKind string // "PrimaryRole" + AllowConnections string // "All", "ReadWrite" +} + +func (o *PrimaryRoleReplicaOption) node() {} +func (o *PrimaryRoleReplicaOption) availabilityReplicaOption() {} + +// SecondaryRoleReplicaOption represents SECONDARY_ROLE option +type SecondaryRoleReplicaOption struct { + OptionKind string // "SecondaryRole" + AllowConnections string // "No", "ReadOnly", "All" +} + +func (o *SecondaryRoleReplicaOption) node() {} +func (o *SecondaryRoleReplicaOption) availabilityReplicaOption() {} diff --git a/ast/create_simple_statements.go b/ast/create_simple_statements.go index 509e3ecd..eaa30c7c 100644 --- a/ast/create_simple_statements.go +++ b/ast/create_simple_statements.go @@ -2,14 +2,15 @@ package ast // CreateDatabaseStatement represents a CREATE DATABASE statement. type CreateDatabaseStatement struct { - DatabaseName *Identifier `json:"DatabaseName,omitempty"` - Options []CreateDatabaseOption `json:"Options,omitempty"` - AttachMode string `json:"AttachMode,omitempty"` // "None", "Attach", "AttachRebuildLog" - CopyOf *MultiPartIdentifier `json:"CopyOf,omitempty"` // For AS COPY OF syntax - FileGroups []*FileGroupDefinition `json:"FileGroups,omitempty"` - LogOn []*FileDeclaration `json:"LogOn,omitempty"` - Collation *Identifier `json:"Collation,omitempty"` - Containment *ContainmentDatabaseOption `json:"Containment,omitempty"` + DatabaseName *Identifier `json:"DatabaseName,omitempty"` + Options []CreateDatabaseOption `json:"Options,omitempty"` + AttachMode string `json:"AttachMode,omitempty"` // "None", "Attach", "AttachRebuildLog", "AttachForceRebuildLog" + CopyOf *MultiPartIdentifier `json:"CopyOf,omitempty"` // For AS COPY OF syntax + FileGroups []*FileGroupDefinition `json:"FileGroups,omitempty"` + LogOn []*FileDeclaration `json:"LogOn,omitempty"` + Collation *Identifier `json:"Collation,omitempty"` + Containment *ContainmentDatabaseOption `json:"Containment,omitempty"` + DatabaseSnapshot *Identifier `json:"DatabaseSnapshot,omitempty"` // For AS SNAPSHOT OF syntax } // ContainmentDatabaseOption represents CONTAINMENT = NONE/PARTIAL @@ -211,6 +212,7 @@ type CreateAsymmetricKeyStatement struct { Name *Identifier `json:"Name,omitempty"` KeySource EncryptionSource `json:"KeySource,omitempty"` EncryptionAlgorithm string `json:"EncryptionAlgorithm,omitempty"` + Owner *Identifier `json:"Owner,omitempty"` Password ScalarExpression `json:"Password,omitempty"` } @@ -396,8 +398,10 @@ type CreateIndexStatement struct { Clustered *bool `json:"Clustered,omitempty"` // nil = not specified, true = CLUSTERED, false = NONCLUSTERED Columns []*ColumnWithSortOrder `json:"Columns,omitempty"` IncludeColumns []*ColumnReferenceExpression `json:"IncludeColumns,omitempty"` + FilterPredicate BooleanExpression `json:"FilterPredicate,omitempty"` IndexOptions []IndexOption `json:"IndexOptions,omitempty"` OnFileGroupOrPartitionScheme *FileGroupOrPartitionScheme `json:"OnFileGroupOrPartitionScheme,omitempty"` + FileStreamOn *IdentifierOrValueExpression `json:"FileStreamOn,omitempty"` } func (s *CreateIndexStatement) node() {} diff --git a/ast/create_table_statement.go b/ast/create_table_statement.go index 02b74442..e8cca24d 100644 --- a/ast/create_table_statement.go +++ b/ast/create_table_statement.go @@ -81,9 +81,13 @@ type DataTypeReference interface { type DefaultConstraintDefinition struct { ConstraintIdentifier *Identifier Expression ScalarExpression + Column *Identifier // For table-level DEFAULT constraint (DEFAULT ... FOR column) + WithValues bool } -func (d *DefaultConstraintDefinition) node() {} +func (d *DefaultConstraintDefinition) node() {} +func (d *DefaultConstraintDefinition) constraintDefinition() {} +func (d *DefaultConstraintDefinition) tableConstraint() {} // IdentityOptions represents IDENTITY options type IdentityOptions struct { @@ -116,12 +120,15 @@ type TableConstraint interface { // IndexDefinition represents an index definition within CREATE TABLE type IndexDefinition struct { - Name *Identifier - Columns []*ColumnWithSortOrder - Unique bool - IndexType *IndexType - IndexOptions []*IndexExpressionOption - IncludeColumns []*ColumnReferenceExpression + Name *Identifier + Columns []*ColumnWithSortOrder + Unique bool + IndexType *IndexType + IndexOptions []IndexOption + IncludeColumns []*ColumnReferenceExpression + FilterPredicate BooleanExpression + OnFileGroupOrPartitionScheme *FileGroupOrPartitionScheme + FileStreamOn *IdentifierOrValueExpression } func (i *IndexDefinition) node() {} diff --git a/ast/create_view_statement.go b/ast/create_view_statement.go index 480c7a2a..aff579cd 100644 --- a/ast/create_view_statement.go +++ b/ast/create_view_statement.go @@ -26,6 +26,21 @@ type CreateOrAlterViewStatement struct { func (c *CreateOrAlterViewStatement) node() {} func (c *CreateOrAlterViewStatement) statement() {} +// AlterViewStatement represents an ALTER VIEW statement. +type AlterViewStatement struct { + SchemaObjectName *SchemaObjectName `json:"SchemaObjectName,omitempty"` + Columns []*Identifier `json:"Columns,omitempty"` + SelectStatement *SelectStatement `json:"SelectStatement,omitempty"` + WithCheckOption bool `json:"WithCheckOption"` + ViewOptions []ViewOption `json:"ViewOptions,omitempty"` + IsMaterialized bool `json:"IsMaterialized"` + IsRebuild bool `json:"IsRebuild"` + IsDisable bool `json:"IsDisable"` +} + +func (a *AlterViewStatement) node() {} +func (a *AlterViewStatement) statement() {} + // ViewOption is an interface for different view option types. type ViewOption interface { viewOption() diff --git a/ast/drop_statements.go b/ast/drop_statements.go index bcd25344..e8da6ad6 100644 --- a/ast/drop_statements.go +++ b/ast/drop_statements.go @@ -293,8 +293,9 @@ func (s *DropRoleStatement) node() {} // DropAssemblyStatement represents a DROP ASSEMBLY statement type DropAssemblyStatement struct { - IsIfExists bool - Objects []*SchemaObjectName + IsIfExists bool + Objects []*SchemaObjectName + WithNoDependents bool } func (s *DropAssemblyStatement) statement() {} diff --git a/ast/exists_predicate.go b/ast/exists_predicate.go new file mode 100644 index 00000000..1101c4fc --- /dev/null +++ b/ast/exists_predicate.go @@ -0,0 +1,9 @@ +package ast + +// ExistsPredicate represents EXISTS (subquery) +type ExistsPredicate struct { + Subquery QueryExpression `json:"Subquery,omitempty"` +} + +func (*ExistsPredicate) node() {} +func (*ExistsPredicate) booleanExpression() {} diff --git a/ast/external_statements.go b/ast/external_statements.go index 344033be..28783dba 100644 --- a/ast/external_statements.go +++ b/ast/external_statements.go @@ -53,12 +53,18 @@ type CreateExternalTableStatement struct { SchemaObjectName *SchemaObjectName ColumnDefinitions []*ExternalTableColumnDefinition DataSource *Identifier - ExternalTableOptions []*ExternalTableLiteralOrIdentifierOption + ExternalTableOptions []ExternalTableOptionItem + SelectStatement *SelectStatement // For CTAS (CREATE TABLE AS SELECT) } func (s *CreateExternalTableStatement) node() {} func (s *CreateExternalTableStatement) statement() {} +// ExternalTableOptionItem is an interface for external table options +type ExternalTableOptionItem interface { + externalTableOptionItem() +} + // ExternalTableColumnDefinition represents a column definition in an external table type ExternalTableColumnDefinition struct { ColumnDefinition *ColumnDefinitionBase @@ -71,6 +77,16 @@ type ExternalTableLiteralOrIdentifierOption struct { Value *IdentifierOrValueExpression } +func (o *ExternalTableLiteralOrIdentifierOption) externalTableOptionItem() {} + +// ExternalTableRejectTypeOption represents a REJECT_TYPE option +type ExternalTableRejectTypeOption struct { + OptionKind string + Value string // Value, Percentage +} + +func (o *ExternalTableRejectTypeOption) externalTableOptionItem() {} + // ExternalTableOption represents a simple option for external table (legacy) type ExternalTableOption struct { OptionKind string diff --git a/ast/file_declaration.go b/ast/file_declaration.go index 9a915fd1..d3873e7c 100644 --- a/ast/file_declaration.go +++ b/ast/file_declaration.go @@ -25,6 +25,14 @@ type FileDeclarationOption interface { fileDeclarationOption() } +// SimpleFileDeclarationOption represents a simple file option like OFFLINE +type SimpleFileDeclarationOption struct { + OptionKind string // "Offline" +} + +func (s *SimpleFileDeclarationOption) node() {} +func (s *SimpleFileDeclarationOption) fileDeclarationOption() {} + // NameFileDeclarationOption represents the NAME option for a file type NameFileDeclarationOption struct { LogicalFileName *IdentifierOrValueExpression diff --git a/ast/fulltext_predicate.go b/ast/fulltext_predicate.go new file mode 100644 index 00000000..59c23aa9 --- /dev/null +++ b/ast/fulltext_predicate.go @@ -0,0 +1,13 @@ +package ast + +// FullTextPredicate represents CONTAINS or FREETEXT predicates in WHERE clauses +type FullTextPredicate struct { + FullTextFunctionType string `json:"FullTextFunctionType,omitempty"` // Contains, FreeText + Columns []*ColumnReferenceExpression `json:"Columns,omitempty"` + Value ScalarExpression `json:"Value,omitempty"` + PropertyName ScalarExpression `json:"PropertyName,omitempty"` + LanguageTerm ScalarExpression `json:"LanguageTerm,omitempty"` +} + +func (*FullTextPredicate) node() {} +func (*FullTextPredicate) booleanExpression() {} diff --git a/ast/grant_statement.go b/ast/grant_statement.go index ad877f4b..273292ca 100644 --- a/ast/grant_statement.go +++ b/ast/grant_statement.go @@ -6,6 +6,7 @@ type GrantStatement struct { Principals []*SecurityPrincipal WithGrantOption bool SecurityTargetObject *SecurityTargetObject + AsClause *Identifier } func (s *GrantStatement) node() {} diff --git a/ast/iif_call.go b/ast/iif_call.go new file mode 100644 index 00000000..ba3ef35d --- /dev/null +++ b/ast/iif_call.go @@ -0,0 +1,11 @@ +package ast + +// IIfCall represents the IIF(condition, true_value, false_value) function +type IIfCall struct { + Predicate BooleanExpression `json:"Predicate,omitempty"` + ThenExpression ScalarExpression `json:"ThenExpression,omitempty"` + ElseExpression ScalarExpression `json:"ElseExpression,omitempty"` +} + +func (*IIfCall) node() {} +func (*IIfCall) scalarExpression() {} diff --git a/ast/parse_call.go b/ast/parse_call.go new file mode 100644 index 00000000..43479a26 --- /dev/null +++ b/ast/parse_call.go @@ -0,0 +1,21 @@ +package ast + +// ParseCall represents the PARSE(string AS type [USING culture]) function +type ParseCall struct { + StringValue ScalarExpression `json:"StringValue,omitempty"` + DataType DataTypeReference `json:"DataType,omitempty"` + Culture ScalarExpression `json:"Culture,omitempty"` +} + +func (*ParseCall) node() {} +func (*ParseCall) scalarExpression() {} + +// TryParseCall represents the TRY_PARSE(string AS type [USING culture]) function +type TryParseCall struct { + StringValue ScalarExpression `json:"StringValue,omitempty"` + DataType DataTypeReference `json:"DataType,omitempty"` + Culture ScalarExpression `json:"Culture,omitempty"` +} + +func (*TryParseCall) node() {} +func (*TryParseCall) scalarExpression() {} diff --git a/ast/remote_data_archive_table_option.go b/ast/remote_data_archive_table_option.go new file mode 100644 index 00000000..756f558f --- /dev/null +++ b/ast/remote_data_archive_table_option.go @@ -0,0 +1,25 @@ +package ast + +// RemoteDataArchiveTableOption represents REMOTE_DATA_ARCHIVE option for CREATE TABLE +type RemoteDataArchiveTableOption struct { + RdaTableOption string // "Enable", "Disable", "DisableWithoutDataRecovery" + MigrationState string // "Paused", "Outbound", "Inbound" + FilterPredicate ScalarExpression // Optional filter predicate function call + OptionKind string // "RemoteDataArchive" +} + +func (r *RemoteDataArchiveTableOption) node() {} +func (r *RemoteDataArchiveTableOption) tableOption() {} + +// RemoteDataArchiveAlterTableOption represents REMOTE_DATA_ARCHIVE option for ALTER TABLE SET +type RemoteDataArchiveAlterTableOption struct { + RdaTableOption string // "Enable", "Disable", "DisableWithoutDataRecovery" + MigrationState string // "Paused", "Outbound", "Inbound" + IsMigrationStateSpecified bool + FilterPredicate ScalarExpression // Optional filter predicate function call + IsFilterPredicateSpecified bool + OptionKind string // "RemoteDataArchive" +} + +func (r *RemoteDataArchiveAlterTableOption) node() {} +func (r *RemoteDataArchiveAlterTableOption) tableOption() {} diff --git a/ast/resource_pool_statement.go b/ast/resource_pool_statement.go index 371900c5..4abd54bd 100644 --- a/ast/resource_pool_statement.go +++ b/ast/resource_pool_statement.go @@ -18,6 +18,15 @@ type AlterResourcePoolStatement struct { func (*AlterResourcePoolStatement) node() {} func (*AlterResourcePoolStatement) statement() {} +// DropResourcePoolStatement represents a DROP RESOURCE POOL statement +type DropResourcePoolStatement struct { + Name *Identifier + IsIfExists bool +} + +func (*DropResourcePoolStatement) node() {} +func (*DropResourcePoolStatement) statement() {} + // ResourcePoolParameter represents a parameter in a resource pool statement type ResourcePoolParameter struct { ParameterType string `json:"ParameterType,omitempty"` // MinCpuPercent, MaxCpuPercent, CapCpuPercent, MinMemoryPercent, MaxMemoryPercent, MinIoPercent, MaxIoPercent, CapIoPercent, Affinity, etc. @@ -37,3 +46,26 @@ type LiteralRange struct { From ScalarExpression `json:"From,omitempty"` To ScalarExpression `json:"To,omitempty"` } + +// AlterExternalResourcePoolStatement represents an ALTER EXTERNAL RESOURCE POOL statement +type AlterExternalResourcePoolStatement struct { + Name *Identifier `json:"Name,omitempty"` + ExternalResourcePoolParameters []*ExternalResourcePoolParameter `json:"ExternalResourcePoolParameters,omitempty"` +} + +func (*AlterExternalResourcePoolStatement) node() {} +func (*AlterExternalResourcePoolStatement) statement() {} + +// ExternalResourcePoolParameter represents a parameter in an external resource pool statement +type ExternalResourcePoolParameter struct { + ParameterType string `json:"ParameterType,omitempty"` // MaxCpuPercent, MaxMemoryPercent, MaxProcesses, Affinity + ParameterValue ScalarExpression `json:"ParameterValue,omitempty"` + AffinitySpecification *ExternalResourcePoolAffinitySpecification `json:"AffinitySpecification,omitempty"` +} + +// ExternalResourcePoolAffinitySpecification represents an AFFINITY specification in an external resource pool +type ExternalResourcePoolAffinitySpecification struct { + AffinityType string `json:"AffinityType,omitempty"` // Cpu, NumaNode + IsAuto bool `json:"IsAuto"` + PoolAffinityRanges []*LiteralRange `json:"PoolAffinityRanges,omitempty"` +} diff --git a/ast/security_target_object.go b/ast/security_target_object.go index bb81f587..46c01687 100644 --- a/ast/security_target_object.go +++ b/ast/security_target_object.go @@ -4,6 +4,7 @@ package ast type SecurityTargetObject struct { ObjectKind string // e.g., "ServerRole", "NotSpecified", "Type", etc. ObjectName *SecurityTargetObjectName + Columns []*Identifier // Column list for column-level permissions } func (s *SecurityTargetObject) node() {} diff --git a/ast/server_audit_statement.go b/ast/server_audit_statement.go index 81e73f59..175c6d49 100644 --- a/ast/server_audit_statement.go +++ b/ast/server_audit_statement.go @@ -79,6 +79,14 @@ type OnOffAuditTargetOption struct { func (o *OnOffAuditTargetOption) auditTargetOption() {} +// RetentionDaysAuditTargetOption represents the RETENTION_DAYS option +type RetentionDaysAuditTargetOption struct { + OptionKind string + Days ScalarExpression +} + +func (o *RetentionDaysAuditTargetOption) auditTargetOption() {} + // AuditOption is an interface for audit options type AuditOption interface { auditOption() @@ -131,3 +139,69 @@ type EventSessionObjectName struct { } func (e *EventSessionObjectName) node() {} + +// CreateServerAuditSpecificationStatement represents a CREATE SERVER AUDIT SPECIFICATION statement +type CreateServerAuditSpecificationStatement struct { + SpecificationName *Identifier + AuditName *Identifier + Parts []*AuditSpecificationPart + AuditState string // NotSet, On, Off +} + +func (s *CreateServerAuditSpecificationStatement) statement() {} +func (s *CreateServerAuditSpecificationStatement) node() {} + +// AlterServerAuditSpecificationStatement represents an ALTER SERVER AUDIT SPECIFICATION statement +type AlterServerAuditSpecificationStatement struct { + SpecificationName *Identifier + AuditName *Identifier + Parts []*AuditSpecificationPart + AuditState string // NotSet, On, Off +} + +func (s *AlterServerAuditSpecificationStatement) statement() {} +func (s *AlterServerAuditSpecificationStatement) node() {} + +// CreateDatabaseAuditSpecificationStatement represents a CREATE DATABASE AUDIT SPECIFICATION statement +type CreateDatabaseAuditSpecificationStatement struct { + SpecificationName *Identifier + AuditName *Identifier + Parts []*AuditSpecificationPart + AuditState string // NotSet, On, Off +} + +func (s *CreateDatabaseAuditSpecificationStatement) statement() {} +func (s *CreateDatabaseAuditSpecificationStatement) node() {} + +// AlterDatabaseAuditSpecificationStatement represents an ALTER DATABASE AUDIT SPECIFICATION statement +type AlterDatabaseAuditSpecificationStatement struct { + SpecificationName *Identifier + AuditName *Identifier + Parts []*AuditSpecificationPart + AuditState string // NotSet, On, Off +} + +func (s *AlterDatabaseAuditSpecificationStatement) statement() {} +func (s *AlterDatabaseAuditSpecificationStatement) node() {} + +// AuditSpecificationPart represents an ADD or DROP part in an audit specification +type AuditSpecificationPart struct { + IsDrop bool + Details AuditSpecificationDetail +} + +func (p *AuditSpecificationPart) node() {} + +// AuditSpecificationDetail is an interface for audit specification details +type AuditSpecificationDetail interface { + Node + auditSpecificationDetail() +} + +// AuditActionGroupReference represents a reference to an audit action group +type AuditActionGroupReference struct { + Group string +} + +func (r *AuditActionGroupReference) node() {} +func (r *AuditActionGroupReference) auditSpecificationDetail() {} diff --git a/ast/subquery_comparison_predicate.go b/ast/subquery_comparison_predicate.go new file mode 100644 index 00000000..04966fb6 --- /dev/null +++ b/ast/subquery_comparison_predicate.go @@ -0,0 +1,13 @@ +package ast + +// SubqueryComparisonPredicate represents a comparison with a subquery using ANY/SOME/ALL. +// Example: col IS DISTINCT FROM SOME (SELECT ...), col > ALL (SELECT ...) +type SubqueryComparisonPredicate struct { + Expression ScalarExpression + ComparisonType string // "IsDistinctFrom", "IsNotDistinctFrom", "Equals", etc. + Subquery *ScalarSubquery + SubqueryComparisonPredicateType string // "Any", "All" +} + +func (s *SubqueryComparisonPredicate) node() {} +func (s *SubqueryComparisonPredicate) booleanExpression() {} diff --git a/parser/marshal.go b/parser/marshal.go index 2338f7c9..33e96dcf 100644 --- a/parser/marshal.go +++ b/parser/marshal.go @@ -82,6 +82,8 @@ func statementToJSON(stmt ast.Statement) jsonNode { return createViewStatementToJSON(s) case *ast.CreateOrAlterViewStatement: return createOrAlterViewStatementToJSON(s) + case *ast.AlterViewStatement: + return alterViewStatementToJSON(s) case *ast.CreateSchemaStatement: return createSchemaStatementToJSON(s) case *ast.CreateProcedureStatement: @@ -144,6 +146,8 @@ func statementToJSON(stmt ast.Statement) jsonNode { return alterDatabaseRemoveFileGroupStatementToJSON(s) case *ast.AlterDatabaseCollateStatement: return alterDatabaseCollateStatementToJSON(s) + case *ast.AlterDatabaseRebuildLogStatement: + return alterDatabaseRebuildLogStatementToJSON(s) case *ast.AlterDatabaseScopedConfigurationClearStatement: return alterDatabaseScopedConfigurationClearStatementToJSON(s) case *ast.AlterResourceGovernorStatement: @@ -152,6 +156,10 @@ func statementToJSON(stmt ast.Statement) jsonNode { return createResourcePoolStatementToJSON(s) case *ast.AlterResourcePoolStatement: return alterResourcePoolStatementToJSON(s) + case *ast.DropResourcePoolStatement: + return dropResourcePoolStatementToJSON(s) + case *ast.AlterExternalResourcePoolStatement: + return alterExternalResourcePoolStatementToJSON(s) case *ast.CreateCryptographicProviderStatement: return createCryptographicProviderStatementToJSON(s) case *ast.CreateColumnMasterKeyStatement: @@ -338,10 +346,20 @@ func statementToJSON(stmt ast.Statement) jsonNode { return createServerRoleStatementToJSON(s) case *ast.AlterServerRoleStatement: return alterServerRoleStatementToJSON(s) + case *ast.CreateAvailabilityGroupStatement: + return createAvailabilityGroupStatementToJSON(s) case *ast.CreateServerAuditStatement: return createServerAuditStatementToJSON(s) case *ast.AlterServerAuditStatement: return alterServerAuditStatementToJSON(s) + case *ast.CreateServerAuditSpecificationStatement: + return createServerAuditSpecificationStatementToJSON(s) + case *ast.AlterServerAuditSpecificationStatement: + return alterServerAuditSpecificationStatementToJSON(s) + case *ast.CreateDatabaseAuditSpecificationStatement: + return createDatabaseAuditSpecificationStatementToJSON(s) + case *ast.AlterDatabaseAuditSpecificationStatement: + return alterDatabaseAuditSpecificationStatementToJSON(s) case *ast.AlterRemoteServiceBindingStatement: return alterRemoteServiceBindingStatementToJSON(s) case *ast.AlterXmlSchemaCollectionStatement: @@ -506,6 +524,10 @@ func statementToJSON(stmt ast.Statement) jsonNode { return alterTableConstraintModificationStatementToJSON(s) case *ast.AlterTableSetStatement: return alterTableSetStatementToJSON(s) + case *ast.AlterTableRebuildStatement: + return alterTableRebuildStatementToJSON(s) + case *ast.AlterTableChangeTrackingModificationStatement: + return alterTableChangeTrackingStatementToJSON(s) case *ast.InsertBulkStatement: return insertBulkStatementToJSON(s) case *ast.BulkInsertStatement: @@ -863,17 +885,71 @@ func alterTableAlterColumnStatementToJSON(s *ast.AlterTableAlterColumnStatement) node["DataType"] = dataTypeReferenceToJSON(s.DataType) } node["AlterTableAlterColumnOption"] = s.AlterTableAlterColumnOption + if s.StorageOptions != nil { + node["StorageOptions"] = columnStorageOptionsToJSON(s.StorageOptions) + } node["IsHidden"] = s.IsHidden + if s.Encryption != nil { + node["Encryption"] = columnEncryptionDefinitionToJSON(s.Encryption) + } if s.Collation != nil { node["Collation"] = identifierToJSON(s.Collation) } node["IsMasked"] = s.IsMasked + if s.MaskingFunction != nil { + node["MaskingFunction"] = scalarExpressionToJSON(s.MaskingFunction) + } if s.SchemaObjectName != nil { node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName) } return node } +func columnEncryptionDefinitionToJSON(e *ast.ColumnEncryptionDefinition) jsonNode { + node := jsonNode{ + "$type": "ColumnEncryptionDefinition", + } + if len(e.Parameters) > 0 { + params := make([]jsonNode, len(e.Parameters)) + for i, p := range e.Parameters { + params[i] = columnEncryptionParameterToJSON(p) + } + node["Parameters"] = params + } + return node +} + +func columnEncryptionParameterToJSON(p ast.ColumnEncryptionParameter) jsonNode { + switch param := p.(type) { + case *ast.ColumnEncryptionKeyNameParameter: + node := jsonNode{ + "$type": "ColumnEncryptionKeyNameParameter", + "ParameterKind": param.ParameterKind, + } + if param.Name != nil { + node["Name"] = identifierToJSON(param.Name) + } + return node + case *ast.ColumnEncryptionTypeParameter: + return jsonNode{ + "$type": "ColumnEncryptionTypeParameter", + "EncryptionType": param.EncryptionType, + "ParameterKind": param.ParameterKind, + } + case *ast.ColumnEncryptionAlgorithmParameter: + node := jsonNode{ + "$type": "ColumnEncryptionAlgorithmParameter", + "ParameterKind": param.ParameterKind, + } + if param.EncryptionAlgorithm != nil { + node["EncryptionAlgorithm"] = scalarExpressionToJSON(param.EncryptionAlgorithm) + } + return node + default: + return jsonNode{"$type": "Unknown"} + } +} + func alterMessageTypeStatementToJSON(s *ast.AlterMessageTypeStatement) jsonNode { node := jsonNode{ "$type": "AlterMessageTypeStatement", @@ -982,12 +1058,18 @@ func alterCredentialStatementToJSON(s *ast.AlterCredentialStatement) jsonNode { func alterDatabaseSetStatementToJSON(s *ast.AlterDatabaseSetStatement) jsonNode { node := jsonNode{ - "$type": "AlterDatabaseSetStatement", - "WithManualCutover": s.WithManualCutover, - "UseCurrent": s.UseCurrent, + "$type": "AlterDatabaseSetStatement", } - if s.DatabaseName != nil { - node["DatabaseName"] = identifierToJSON(s.DatabaseName) + if s.Termination != nil { + termNode := jsonNode{ + "$type": "AlterDatabaseTermination", + "ImmediateRollback": s.Termination.ImmediateRollback, + "NoWait": s.Termination.NoWait, + } + if s.Termination.RollbackAfter != nil { + termNode["RollbackAfter"] = scalarExpressionToJSON(s.Termination.RollbackAfter) + } + node["Termination"] = termNode } if len(s.Options) > 0 { opts := make([]jsonNode, len(s.Options)) @@ -996,6 +1078,11 @@ func alterDatabaseSetStatementToJSON(s *ast.AlterDatabaseSetStatement) jsonNode } node["Options"] = opts } + node["WithManualCutover"] = s.WithManualCutover + if s.DatabaseName != nil { + node["DatabaseName"] = identifierToJSON(s.DatabaseName) + } + node["UseCurrent"] = s.UseCurrent return node } @@ -1021,11 +1108,13 @@ func databaseOptionToJSON(opt ast.DatabaseOption) jsonNode { } case *ast.AutoCreateStatisticsDatabaseOption: node := jsonNode{ - "$type": "AutoCreateStatisticsDatabaseOption", + "$type": "AutoCreateStatisticsDatabaseOption", + "HasIncremental": o.HasIncremental, } - if o.HasIncremental { - node["HasIncremental"] = o.HasIncremental + if o.IncrementalState != "" { node["IncrementalState"] = o.IncrementalState + } else { + node["IncrementalState"] = "NotSet" } node["OptionState"] = o.OptionState node["OptionKind"] = o.OptionKind @@ -1055,6 +1144,17 @@ func databaseOptionToJSON(opt ast.DatabaseOption) jsonNode { node["OptionKind"] = o.OptionKind } return node + case *ast.ElasticPoolSpecification: + node := jsonNode{ + "$type": "ElasticPoolSpecification", + } + if o.ElasticPoolName != nil { + node["ElasticPoolName"] = identifierToJSON(o.ElasticPoolName) + } + if o.OptionKind != "" { + node["OptionKind"] = o.OptionKind + } + return node case *ast.RemoteDataArchiveDatabaseOption: node := jsonNode{ "$type": "RemoteDataArchiveDatabaseOption", @@ -1083,6 +1183,63 @@ func databaseOptionToJSON(opt ast.DatabaseOption) jsonNode { node["Details"] = details } return node + case *ast.RecoveryDatabaseOption: + return jsonNode{ + "$type": "RecoveryDatabaseOption", + "Value": o.Value, + "OptionKind": o.OptionKind, + } + case *ast.CursorDefaultDatabaseOption: + return jsonNode{ + "$type": "CursorDefaultDatabaseOption", + "IsLocal": o.IsLocal, + "OptionKind": o.OptionKind, + } + case *ast.SimpleDatabaseOption: + return jsonNode{ + "$type": "DatabaseOption", + "OptionKind": o.OptionKind, + } + case *ast.GenericDatabaseOption: + return jsonNode{ + "$type": "DatabaseOption", + "OptionKind": o.OptionKind, + } + case *ast.PageVerifyDatabaseOption: + return jsonNode{ + "$type": "PageVerifyDatabaseOption", + "Value": o.Value, + "OptionKind": o.OptionKind, + } + case *ast.PartnerDatabaseOption: + node := jsonNode{ + "$type": "PartnerDatabaseOption", + "PartnerOption": o.PartnerOption, + "OptionKind": o.OptionKind, + } + if o.PartnerServer != nil { + node["PartnerServer"] = scalarExpressionToJSON(o.PartnerServer) + } + if o.Timeout != nil { + node["Timeout"] = scalarExpressionToJSON(o.Timeout) + } + return node + case *ast.WitnessDatabaseOption: + node := jsonNode{ + "$type": "WitnessDatabaseOption", + "IsOff": o.IsOff, + "OptionKind": o.OptionKind, + } + if o.WitnessServer != nil { + node["WitnessServer"] = scalarExpressionToJSON(o.WitnessServer) + } + return node + case *ast.ParameterizationDatabaseOption: + return jsonNode{ + "$type": "ParameterizationDatabaseOption", + "IsSimple": o.IsSimple, + "OptionKind": o.OptionKind, + } default: return jsonNode{"$type": "UnknownDatabaseOption"} } @@ -1154,7 +1311,7 @@ func indexDefinitionToJSON(idx *ast.IndexDefinition) jsonNode { if len(idx.IndexOptions) > 0 { options := make([]jsonNode, len(idx.IndexOptions)) for i, o := range idx.IndexOptions { - options[i] = indexExpressionOptionToJSON(o) + options[i] = indexOptionToJSON(o) } node["IndexOptions"] = options } @@ -1172,6 +1329,15 @@ func indexDefinitionToJSON(idx *ast.IndexDefinition) jsonNode { } node["IncludeColumns"] = cols } + if idx.FilterPredicate != nil { + node["FilterPredicate"] = booleanExpressionToJSON(idx.FilterPredicate) + } + if idx.OnFileGroupOrPartitionScheme != nil { + node["OnFileGroupOrPartitionScheme"] = fileGroupOrPartitionSchemeToJSON(idx.OnFileGroupOrPartitionScheme) + } + if idx.FileStreamOn != nil { + node["FileStreamOn"] = identifierOrValueExpressionToJSON(idx.FileStreamOn) + } return node } @@ -1815,6 +1981,48 @@ func scalarExpressionToJSON(expr ast.ScalarExpression) jsonNode { node["OverClause"] = overClauseToJSON(e.OverClause) } return node + case *ast.IIfCall: + node := jsonNode{ + "$type": "IIfCall", + } + if e.Predicate != nil { + node["Predicate"] = booleanExpressionToJSON(e.Predicate) + } + if e.ThenExpression != nil { + node["ThenExpression"] = scalarExpressionToJSON(e.ThenExpression) + } + if e.ElseExpression != nil { + node["ElseExpression"] = scalarExpressionToJSON(e.ElseExpression) + } + return node + case *ast.ParseCall: + node := jsonNode{ + "$type": "ParseCall", + } + if e.StringValue != nil { + node["StringValue"] = scalarExpressionToJSON(e.StringValue) + } + if e.DataType != nil { + node["DataType"] = dataTypeReferenceToJSON(e.DataType) + } + if e.Culture != nil { + node["Culture"] = scalarExpressionToJSON(e.Culture) + } + return node + case *ast.TryParseCall: + node := jsonNode{ + "$type": "TryParseCall", + } + if e.StringValue != nil { + node["StringValue"] = scalarExpressionToJSON(e.StringValue) + } + if e.DataType != nil { + node["DataType"] = dataTypeReferenceToJSON(e.DataType) + } + if e.Culture != nil { + node["Culture"] = scalarExpressionToJSON(e.Culture) + } + return node case *ast.VariableReference: node := jsonNode{ "$type": "VariableReference", @@ -2415,6 +2623,19 @@ func booleanExpressionToJSON(expr ast.BooleanExpression) jsonNode { } node["IsNot"] = e.IsNot return node + case *ast.SubqueryComparisonPredicate: + node := jsonNode{ + "$type": "SubqueryComparisonPredicate", + "ComparisonType": e.ComparisonType, + "SubqueryComparisonPredicateType": e.SubqueryComparisonPredicateType, + } + if e.Expression != nil { + node["Expression"] = scalarExpressionToJSON(e.Expression) + } + if e.Subquery != nil { + node["Subquery"] = scalarExpressionToJSON(e.Subquery) + } + return node case *ast.BooleanInExpression: node := jsonNode{ "$type": "InPredicate", @@ -2489,6 +2710,38 @@ func booleanExpressionToJSON(expr ast.BooleanExpression) jsonNode { node["Expression"] = graphMatchExpressionToJSON(e.Expression) } return node + case *ast.FullTextPredicate: + node := jsonNode{ + "$type": "FullTextPredicate", + } + if e.FullTextFunctionType != "" { + node["FullTextFunctionType"] = e.FullTextFunctionType + } + if len(e.Columns) > 0 { + cols := make([]jsonNode, len(e.Columns)) + for i, col := range e.Columns { + cols[i] = columnReferenceExpressionToJSON(col) + } + node["Columns"] = cols + } + if e.Value != nil { + node["Value"] = scalarExpressionToJSON(e.Value) + } + if e.PropertyName != nil { + node["PropertyName"] = scalarExpressionToJSON(e.PropertyName) + } + if e.LanguageTerm != nil { + node["LanguageTerm"] = scalarExpressionToJSON(e.LanguageTerm) + } + return node + case *ast.ExistsPredicate: + node := jsonNode{ + "$type": "ExistsPredicate", + } + if e.Subquery != nil { + node["Subquery"] = queryExpressionToJSON(e.Subquery) + } + return node default: return jsonNode{"$type": "UnknownBooleanExpression"} } @@ -3472,6 +3725,12 @@ func atomicBlockOptionToJSON(o ast.AtomicBlockOption) jsonNode { node["Value"] = scalarExpressionToJSON(opt.Value) } return node + case *ast.OnOffAtomicBlockOption: + return jsonNode{ + "$type": "OnOffAtomicBlockOption", + "OptionState": opt.OptionState, + "OptionKind": opt.OptionKind, + } default: return jsonNode{"$type": "UnknownAtomicBlockOption"} } @@ -3614,6 +3873,37 @@ func createOrAlterViewStatementToJSON(s *ast.CreateOrAlterViewStatement) jsonNod return node } +func alterViewStatementToJSON(s *ast.AlterViewStatement) jsonNode { + node := jsonNode{ + "$type": "AlterViewStatement", + } + node["IsRebuild"] = s.IsRebuild + node["IsDisable"] = s.IsDisable + if s.SchemaObjectName != nil { + node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName) + } + if len(s.Columns) > 0 { + cols := make([]jsonNode, len(s.Columns)) + for i, c := range s.Columns { + cols[i] = identifierToJSON(c) + } + node["Columns"] = cols + } + if len(s.ViewOptions) > 0 { + opts := make([]jsonNode, len(s.ViewOptions)) + for i, opt := range s.ViewOptions { + opts[i] = viewOptionToJSON(opt) + } + node["ViewOptions"] = opts + } + if s.SelectStatement != nil { + node["SelectStatement"] = selectStatementToJSON(s.SelectStatement) + } + node["WithCheckOption"] = s.WithCheckOption + node["IsMaterialized"] = s.IsMaterialized + return node +} + func viewOptionToJSON(opt ast.ViewOption) jsonNode { switch o := opt.(type) { case *ast.ViewStatementOption: @@ -4007,6 +4297,26 @@ func (p *Parser) parseCreateTableStatement() (*ast.CreateTableStatement, error) OptionKind: "MemoryOptimized", OptionState: state, }) + } else if optionName == "DURABILITY" { + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + valueUpper := strings.ToUpper(p.curTok.Literal) + durabilityKind := "SchemaOnly" + if valueUpper == "SCHEMA_AND_DATA" { + durabilityKind = "SchemaAndData" + } + p.nextToken() // consume value + stmt.Options = append(stmt.Options, &ast.DurabilityTableOption{ + OptionKind: "Durability", + DurabilityTableOptionKind: durabilityKind, + }) + } else if optionName == "REMOTE_DATA_ARCHIVE" { + opt, err := p.parseRemoteDataArchiveTableOption(false) + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, opt) } else { // Skip unknown option value if p.curTok.Type == TokenEquals { @@ -4214,6 +4524,20 @@ func (p *Parser) parseCreateTableOptions(stmt *ast.CreateTableStatement) (*ast.C OptionKind: "MemoryOptimized", OptionState: state, }) + } else if optionName == "DURABILITY" { + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + valueUpper := strings.ToUpper(p.curTok.Literal) + durabilityKind := "SchemaOnly" + if valueUpper == "SCHEMA_AND_DATA" { + durabilityKind = "SchemaAndData" + } + p.nextToken() // consume value + stmt.Options = append(stmt.Options, &ast.DurabilityTableOption{ + OptionKind: "Durability", + DurabilityTableOptionKind: durabilityKind, + }) } else if optionName == "FILETABLE_PRIMARY_KEY_CONSTRAINT_NAME" { if p.curTok.Type == TokenEquals { p.nextToken() // consume = @@ -4241,6 +4565,12 @@ func (p *Parser) parseCreateTableOptions(stmt *ast.CreateTableStatement) (*ast.C OptionKind: "FileTableFullPathUniqueConstraintName", Value: constraintName, }) + } else if optionName == "REMOTE_DATA_ARCHIVE" { + opt, err := p.parseRemoteDataArchiveTableOption(false) + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, opt) } else { // Skip unknown option value if p.curTok.Type == TokenEquals { @@ -4270,6 +4600,101 @@ func (p *Parser) parseCreateTableOptions(stmt *ast.CreateTableStatement) (*ast.C return stmt, nil } +// parseRemoteDataArchiveTableOption parses REMOTE_DATA_ARCHIVE = ON/OFF (options...) for tables +// isAlterTable indicates if this is for ALTER TABLE SET (which uses RemoteDataArchiveAlterTableOption) +func (p *Parser) parseRemoteDataArchiveTableOption(isAlterTable bool) (ast.TableOption, error) { + // curTok should be = or ( + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + + // Parse ON, OFF, or OFF_WITHOUT_DATA_RECOVERY + rdaOption := "Enable" + stateUpper := strings.ToUpper(p.curTok.Literal) + if stateUpper == "ON" { + rdaOption = "Enable" + p.nextToken() + } else if stateUpper == "OFF" { + rdaOption = "Disable" + p.nextToken() + } else if stateUpper == "OFF_WITHOUT_DATA_RECOVERY" { + rdaOption = "OffWithoutDataRecovery" + p.nextToken() + } + + var migrationState string + var filterPredicate ast.ScalarExpression + isMigrationStateSpecified := false + isFilterPredicateSpecified := false + + // Parse options in parentheses + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optName := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume option name + + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + + switch optName { + case "MIGRATION_STATE": + isMigrationStateSpecified = true + msUpper := strings.ToUpper(p.curTok.Literal) + if msUpper == "PAUSED" { + migrationState = "Paused" + } else if msUpper == "OUTBOUND" { + migrationState = "Outbound" + } else if msUpper == "INBOUND" { + migrationState = "Inbound" + } + p.nextToken() + case "FILTER_PREDICATE": + isFilterPredicateSpecified = true + if strings.ToUpper(p.curTok.Literal) == "NULL" { + // When FILTER_PREDICATE = NULL, filterPredicate stays nil + p.nextToken() + } else { + // Parse function call like dbo.f1(c1) + expr, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + filterPredicate = expr + } + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + + if isAlterTable { + return &ast.RemoteDataArchiveAlterTableOption{ + RdaTableOption: rdaOption, + MigrationState: migrationState, + IsMigrationStateSpecified: isMigrationStateSpecified, + FilterPredicate: filterPredicate, + IsFilterPredicateSpecified: isFilterPredicateSpecified, + OptionKind: "RemoteDataArchive", + }, nil + } + + return &ast.RemoteDataArchiveTableOption{ + RdaTableOption: rdaOption, + MigrationState: migrationState, + FilterPredicate: filterPredicate, + OptionKind: "RemoteDataArchive", + }, nil +} + // parseMergeStatement parses a MERGE statement func (p *Parser) parseMergeStatement() (*ast.MergeStatement, error) { // Consume MERGE @@ -4910,6 +5335,14 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) { return nil, err } defaultConstraint.Expression = expr + // Parse optional WITH VALUES + if p.curTok.Type == TokenWith { + p.nextToken() // consume WITH + if strings.ToUpper(p.curTok.Literal) == "VALUES" { + p.nextToken() // consume VALUES + defaultConstraint.WithValues = true + } + } col.DefaultConstraint = defaultConstraint } else if upperLit == "CHECK" { p.nextToken() // consume CHECK @@ -4966,6 +5399,33 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) { p.nextToken() // consume ) } } + // Parse ON DELETE and ON UPDATE actions + for { + actionUpperLit := strings.ToUpper(p.curTok.Literal) + if actionUpperLit == "ON" { + p.nextToken() // consume ON + actionType := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume DELETE or UPDATE + + action := p.parseForeignKeyAction() + if actionType == "DELETE" { + constraint.DeleteAction = action + } else if actionType == "UPDATE" { + constraint.UpdateAction = action + } + } else if actionUpperLit == "NOT" { + p.nextToken() // consume NOT + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "REPLICATION" { + p.nextToken() // consume REPLICATION + constraint.NotForReplication = true + } + } + } else { + break + } + } col.Constraints = append(col.Constraints, constraint) } else if upperLit == "CONSTRAINT" { p.nextToken() // consume CONSTRAINT @@ -4981,18 +5441,37 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) { indexDef := &ast.IndexDefinition{ IndexType: &ast.IndexType{}, } - // Parse index name (skip if it's CLUSTERED/NONCLUSTERED) + // Parse index name (skip if it's CLUSTERED/NONCLUSTERED/UNIQUE) idxUpper := strings.ToUpper(p.curTok.Literal) - if p.curTok.Type == TokenIdent && idxUpper != "CLUSTERED" && idxUpper != "NONCLUSTERED" && p.curTok.Type != TokenLParen { + if p.curTok.Type == TokenIdent && idxUpper != "CLUSTERED" && idxUpper != "NONCLUSTERED" && idxUpper != "UNIQUE" && p.curTok.Type != TokenLParen { indexDef.Name = p.parseIdentifier() } - // Parse optional CLUSTERED/NONCLUSTERED + // Parse optional UNIQUE + if strings.ToUpper(p.curTok.Literal) == "UNIQUE" { + indexDef.Unique = true + p.nextToken() + } + // Parse optional CLUSTERED/NONCLUSTERED [HASH] if strings.ToUpper(p.curTok.Literal) == "CLUSTERED" { indexDef.IndexType.IndexTypeKind = "Clustered" p.nextToken() + // Check for HASH + if strings.ToUpper(p.curTok.Literal) == "HASH" { + indexDef.IndexType.IndexTypeKind = "ClusteredHash" + p.nextToken() + } } else if strings.ToUpper(p.curTok.Literal) == "NONCLUSTERED" { indexDef.IndexType.IndexTypeKind = "NonClustered" p.nextToken() + // Check for HASH + if strings.ToUpper(p.curTok.Literal) == "HASH" { + indexDef.IndexType.IndexTypeKind = "NonClusteredHash" + p.nextToken() + } + } else if strings.ToUpper(p.curTok.Literal) == "HASH" { + // Standalone HASH is treated as NonClusteredHash + indexDef.IndexType.IndexTypeKind = "NonClusteredHash" + p.nextToken() } // Parse optional column list: (col1 [ASC|DESC], ...) if p.curTok.Type == TokenLParen { @@ -5031,15 +5510,164 @@ func (p *Parser) parseColumnDefinition() (*ast.ColumnDefinition, error) { p.nextToken() // consume ) } } - // Parse optional INCLUDE clause - if strings.ToUpper(p.curTok.Literal) == "INCLUDE" { - p.nextToken() // consume INCLUDE + // Parse optional WITH (index_options) + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH if p.curTok.Type == TokenLParen { p.nextToken() // consume ( for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { - colRef := &ast.ColumnReferenceExpression{ - ColumnType: "Regular", - MultiPartIdentifier: &ast.MultiPartIdentifier{ + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume option name + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + if optionName == "BUCKET_COUNT" { + opt := &ast.IndexExpressionOption{ + OptionKind: "BucketCount", + Expression: &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + }, + } + indexDef.IndexOptions = append(indexDef.IndexOptions, opt) + p.nextToken() + } else if optionName == "DATA_COMPRESSION" { + // Parse DATA_COMPRESSION = level [ON PARTITIONS(...)] + compressionLevel := "None" + levelUpper := strings.ToUpper(p.curTok.Literal) + switch levelUpper { + case "NONE": + compressionLevel = "None" + case "ROW": + compressionLevel = "Row" + case "PAGE": + compressionLevel = "Page" + case "COLUMNSTORE": + compressionLevel = "ColumnStore" + case "COLUMNSTORE_ARCHIVE": + compressionLevel = "ColumnStoreArchive" + } + p.nextToken() // consume compression level + opt := &ast.DataCompressionOption{ + CompressionLevel: compressionLevel, + OptionKind: "DataCompression", + } + // Check for optional ON PARTITIONS(range) + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + if strings.ToUpper(p.curTok.Literal) == "PARTITIONS" { + p.nextToken() // consume PARTITIONS + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + partRange := &ast.CompressionPartitionRange{} + partRange.From = &ast.IntegerLiteral{LiteralType: "Integer", Value: p.curTok.Literal} + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "TO" { + p.nextToken() // consume TO + partRange.To = &ast.IntegerLiteral{LiteralType: "Integer", Value: p.curTok.Literal} + p.nextToken() + } + opt.PartitionRanges = append(opt.PartitionRanges, partRange) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } + } + indexDef.IndexOptions = append(indexDef.IndexOptions, opt) + } else if optionName == "PAD_INDEX" || optionName == "STATISTICS_NORECOMPUTE" || + optionName == "ALLOW_ROW_LOCKS" || optionName == "ALLOW_PAGE_LOCKS" || + optionName == "DROP_EXISTING" || optionName == "SORT_IN_TEMPDB" { + // ON/OFF options + stateUpper := strings.ToUpper(p.curTok.Literal) + optState := "On" + if stateUpper == "OFF" { + optState = "Off" + } + p.nextToken() + optKind := map[string]string{ + "PAD_INDEX": "PadIndex", + "STATISTICS_NORECOMPUTE": "StatisticsNoRecompute", + "ALLOW_ROW_LOCKS": "AllowRowLocks", + "ALLOW_PAGE_LOCKS": "AllowPageLocks", + "DROP_EXISTING": "DropExisting", + "SORT_IN_TEMPDB": "SortInTempDB", + }[optionName] + indexDef.IndexOptions = append(indexDef.IndexOptions, &ast.IndexStateOption{ + OptionKind: optKind, + OptionState: optState, + }) + } else if optionName == "IGNORE_DUP_KEY" { + stateUpper := strings.ToUpper(p.curTok.Literal) + optState := "On" + if stateUpper == "OFF" { + optState = "Off" + } + p.nextToken() + indexDef.IndexOptions = append(indexDef.IndexOptions, &ast.IgnoreDupKeyIndexOption{ + OptionKind: "IgnoreDupKey", + OptionState: optState, + }) + } else if optionName == "FILLFACTOR" || optionName == "MAXDOP" { + // Integer expression options + optKind := "FillFactor" + if optionName == "MAXDOP" { + optKind = "MaxDop" + } + indexDef.IndexOptions = append(indexDef.IndexOptions, &ast.IndexExpressionOption{ + OptionKind: optKind, + Expression: &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + }, + }) + p.nextToken() + } else { + // Skip other options + p.nextToken() + } + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } + // Parse optional ON filegroup for inline index + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + fg, _ := p.parseFileGroupOrPartitionScheme() + indexDef.OnFileGroupOrPartitionScheme = fg + } + // Parse optional FILESTREAM_ON for inline index + if strings.ToUpper(p.curTok.Literal) == "FILESTREAM_ON" { + p.nextToken() // consume FILESTREAM_ON + ident := p.parseIdentifier() + indexDef.FileStreamOn = &ast.IdentifierOrValueExpression{ + Value: ident.Value, + Identifier: ident, + } + } + // Parse optional INCLUDE clause + if strings.ToUpper(p.curTok.Literal) == "INCLUDE" { + p.nextToken() // consume INCLUDE + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + colRef := &ast.ColumnReferenceExpression{ + ColumnType: "Regular", + MultiPartIdentifier: &ast.MultiPartIdentifier{ Identifiers: []*ast.Identifier{p.parseIdentifier()}, }, } @@ -5432,9 +6060,69 @@ func (p *Parser) parseForeignKeyConstraint() (*ast.ForeignKeyConstraintDefinitio } } + // Parse ON DELETE and ON UPDATE actions + for { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "ON" { + p.nextToken() // consume ON + actionType := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume DELETE or UPDATE + + // Parse action: NO ACTION, CASCADE, SET NULL, SET DEFAULT + action := p.parseForeignKeyAction() + + if actionType == "DELETE" { + constraint.DeleteAction = action + } else if actionType == "UPDATE" { + constraint.UpdateAction = action + } + } else if upperLit == "NOT" { + // NOT FOR REPLICATION + p.nextToken() // consume NOT + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "REPLICATION" { + p.nextToken() // consume REPLICATION + constraint.NotForReplication = true + } + } + } else { + break + } + } + return constraint, nil } +// parseForeignKeyAction parses CASCADE, NO ACTION, SET NULL, SET DEFAULT +func (p *Parser) parseForeignKeyAction() string { + upperLit := strings.ToUpper(p.curTok.Literal) + + switch upperLit { + case "CASCADE": + p.nextToken() + return "Cascade" + case "NO": + p.nextToken() // consume NO + if strings.ToUpper(p.curTok.Literal) == "ACTION" { + p.nextToken() // consume ACTION + } + return "NoAction" + case "SET": + p.nextToken() // consume SET + setType := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume NULL or DEFAULT + if setType == "NULL" { + return "SetNull" + } else if setType == "DEFAULT" { + return "SetDefault" + } + return "NotSpecified" + default: + return "NotSpecified" + } +} + // parseCheckConstraint parses CHECK (expression) func (p *Parser) parseCheckConstraint() (*ast.CheckConstraintDefinition, error) { // Consume CHECK @@ -5549,12 +6237,32 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) { p.curTok.Type == TokenSelect || p.curTok.Type == TokenInsert || p.curTok.Type == TokenUpdate || p.curTok.Type == TokenDelete || p.curTok.Type == TokenAlter || p.curTok.Type == TokenExecute || - p.curTok.Type == TokenDrop || p.curTok.Type == TokenExternal { + p.curTok.Type == TokenDrop || p.curTok.Type == TokenExternal || + p.curTok.Type == TokenAll || p.curTok.Type == TokenExec || + p.curTok.Type == TokenDatabase || p.curTok.Type == TokenTable || + p.curTok.Type == TokenFunction || p.curTok.Type == TokenBackup || + p.curTok.Type == TokenDefault || p.curTok.Type == TokenTrigger || + p.curTok.Type == TokenSchema { perm.Identifiers = append(perm.Identifiers, &ast.Identifier{ Value: p.curTok.Literal, QuoteType: "NotQuoted", }) p.nextToken() + } else if p.curTok.Type == TokenLParen { + // Column list for permission (e.g., SELECT (c1, c2)) + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + col := p.parseIdentifier() + perm.Columns = append(perm.Columns, col) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } } else if p.curTok.Type == TokenComma { stmt.Permissions = append(stmt.Permissions, perm) perm = &ast.Permission{} @@ -5690,15 +6398,18 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) { stmt.SecurityTargetObject.ObjectKind = "User" } - // Expect :: + // Parse object name if p.curTok.Type == TokenColonColon { p.nextToken() // consume :: + } - // Parse object name as multi-part identifier + // Parse object name as multi-part identifier + // This handles both "OBJECT::name" and plain "..name" syntax + if p.curTok.Type == TokenDot || p.curTok.Type == TokenIdent || p.curTok.Type == TokenLBracket { stmt.SecurityTargetObject.ObjectName = &ast.SecurityTargetObjectName{} multiPart := &ast.MultiPartIdentifier{} for { - // Handle double dots (e.g., a.b..d) by adding empty identifier + // Handle double dots (e.g., ..t1) by adding empty identifier if p.curTok.Type == TokenDot { multiPart.Identifiers = append(multiPart.Identifiers, &ast.Identifier{ Value: "", @@ -5717,6 +6428,23 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) { multiPart.Count = len(multiPart.Identifiers) stmt.SecurityTargetObject.ObjectName.MultiPartIdentifier = multiPart } + + // Parse optional column list (c1, c2, ...) + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + col := p.parseIdentifier() + stmt.SecurityTargetObject.Columns = append(stmt.SecurityTargetObject.Columns, col) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } } // Expect TO @@ -5761,6 +6489,12 @@ func (p *Parser) parseGrantStatement() (*ast.GrantStatement, error) { stmt.WithGrantOption = true } + // Check for AS clause + if strings.ToUpper(p.curTok.Literal) == "AS" { + p.nextToken() // consume AS + stmt.AsClause = p.parseIdentifier() + } + // Skip optional semicolon if p.curTok.Type == TokenSemicolon { p.nextToken() @@ -5796,7 +6530,11 @@ func (p *Parser) parseRevokeStatement() (*ast.RevokeStatement, error) { p.curTok.Type == TokenUpdate || p.curTok.Type == TokenDelete || p.curTok.Type == TokenAlter || p.curTok.Type == TokenExecute || p.curTok.Type == TokenDrop || p.curTok.Type == TokenExternal || - p.curTok.Type == TokenAll { + p.curTok.Type == TokenAll || p.curTok.Type == TokenExec || + p.curTok.Type == TokenDatabase || p.curTok.Type == TokenTable || + p.curTok.Type == TokenFunction || p.curTok.Type == TokenBackup || + p.curTok.Type == TokenDefault || p.curTok.Type == TokenTrigger || + p.curTok.Type == TokenSchema { perm.Identifiers = append(perm.Identifiers, &ast.Identifier{ Value: p.curTok.Literal, QuoteType: "NotQuoted", @@ -5806,13 +6544,9 @@ func (p *Parser) parseRevokeStatement() (*ast.RevokeStatement, error) { // Parse column list for permission p.nextToken() // consume ( for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { - if p.curTok.Type == TokenIdent { - perm.Columns = append(perm.Columns, &ast.Identifier{ - Value: p.curTok.Literal, - QuoteType: "NotQuoted", - }) - p.nextToken() - } else if p.curTok.Type == TokenComma { + col := p.parseIdentifier() + perm.Columns = append(perm.Columns, col) + if p.curTok.Type == TokenComma { p.nextToken() } else { break @@ -5955,15 +6689,18 @@ func (p *Parser) parseRevokeStatement() (*ast.RevokeStatement, error) { stmt.SecurityTargetObject.ObjectKind = "User" } - // Expect :: + // Parse object name if p.curTok.Type == TokenColonColon { p.nextToken() // consume :: + } - // Parse object name as multi-part identifier + // Parse object name as multi-part identifier + // This handles both "OBJECT::name" and plain "..name" syntax + if p.curTok.Type == TokenDot || p.curTok.Type == TokenIdent || p.curTok.Type == TokenLBracket { stmt.SecurityTargetObject.ObjectName = &ast.SecurityTargetObjectName{} multiPart := &ast.MultiPartIdentifier{} for { - // Handle double dots (e.g., a.b..d) by adding empty identifier + // Handle double dots (e.g., ..t1) by adding empty identifier if p.curTok.Type == TokenDot { multiPart.Identifiers = append(multiPart.Identifiers, &ast.Identifier{ Value: "", @@ -5982,6 +6719,23 @@ func (p *Parser) parseRevokeStatement() (*ast.RevokeStatement, error) { multiPart.Count = len(multiPart.Identifiers) stmt.SecurityTargetObject.ObjectName.MultiPartIdentifier = multiPart } + + // Parse optional column list (c1, c2, ...) + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + col := p.parseIdentifier() + stmt.SecurityTargetObject.Columns = append(stmt.SecurityTargetObject.Columns, col) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } } // Expect TO or FROM @@ -6047,12 +6801,32 @@ func (p *Parser) parseDenyStatement() (*ast.DenyStatement, error) { p.curTok.Type == TokenSelect || p.curTok.Type == TokenInsert || p.curTok.Type == TokenUpdate || p.curTok.Type == TokenDelete || p.curTok.Type == TokenAlter || p.curTok.Type == TokenExecute || - p.curTok.Type == TokenDrop || p.curTok.Type == TokenExternal { + p.curTok.Type == TokenDrop || p.curTok.Type == TokenExternal || + p.curTok.Type == TokenAll || p.curTok.Type == TokenExec || + p.curTok.Type == TokenDatabase || p.curTok.Type == TokenTable || + p.curTok.Type == TokenFunction || p.curTok.Type == TokenBackup || + p.curTok.Type == TokenDefault || p.curTok.Type == TokenTrigger || + p.curTok.Type == TokenSchema { perm.Identifiers = append(perm.Identifiers, &ast.Identifier{ Value: p.curTok.Literal, QuoteType: "NotQuoted", }) p.nextToken() + } else if p.curTok.Type == TokenLParen { + // Column list for permission (e.g., SELECT (c1, c2)) + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + col := p.parseIdentifier() + perm.Columns = append(perm.Columns, col) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } } else if p.curTok.Type == TokenComma { stmt.Permissions = append(stmt.Permissions, perm) perm = &ast.Permission{} @@ -6187,15 +6961,18 @@ func (p *Parser) parseDenyStatement() (*ast.DenyStatement, error) { stmt.SecurityTargetObject.ObjectKind = "User" } - // Expect :: + // Parse object name if p.curTok.Type == TokenColonColon { p.nextToken() // consume :: + } - // Parse object name as multi-part identifier + // Parse object name as multi-part identifier + // This handles both "OBJECT::name" and plain "..name" syntax + if p.curTok.Type == TokenDot || p.curTok.Type == TokenIdent || p.curTok.Type == TokenLBracket { stmt.SecurityTargetObject.ObjectName = &ast.SecurityTargetObjectName{} multiPart := &ast.MultiPartIdentifier{} for { - // Handle double dots (e.g., a.b..d) by adding empty identifier + // Handle double dots (e.g., ..t1) by adding empty identifier if p.curTok.Type == TokenDot { multiPart.Identifiers = append(multiPart.Identifiers, &ast.Identifier{ Value: "", @@ -6214,6 +6991,23 @@ func (p *Parser) parseDenyStatement() (*ast.DenyStatement, error) { multiPart.Count = len(multiPart.Identifiers) stmt.SecurityTargetObject.ObjectName.MultiPartIdentifier = multiPart } + + // Parse optional column list (c1, c2, ...) + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + col := p.parseIdentifier() + stmt.SecurityTargetObject.Columns = append(stmt.SecurityTargetObject.Columns, col) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } } // Expect TO @@ -6326,6 +7120,12 @@ func tableOptionToJSON(opt ast.TableOption) jsonNode { "OptionKind": o.OptionKind, "OptionState": o.OptionState, } + case *ast.DurabilityTableOption: + return jsonNode{ + "$type": "DurabilityTableOption", + "OptionKind": o.OptionKind, + "DurabilityTableOptionKind": o.DurabilityTableOptionKind, + } case *ast.FileTableDirectoryTableOption: node := jsonNode{ "$type": "FileTableDirectoryTableOption", @@ -6353,6 +7153,45 @@ func tableOptionToJSON(opt ast.TableOption) jsonNode { node["Value"] = identifierToJSON(o.Value) } return node + case *ast.LockEscalationTableOption: + return jsonNode{ + "$type": "LockEscalationTableOption", + "Value": o.Value, + "OptionKind": o.OptionKind, + } + case *ast.FileStreamOnTableOption: + node := jsonNode{ + "$type": "FileStreamOnTableOption", + "OptionKind": o.OptionKind, + } + if o.Value != nil { + node["Value"] = identifierOrValueExpressionToJSON(o.Value) + } + return node + case *ast.RemoteDataArchiveTableOption: + node := jsonNode{ + "$type": "RemoteDataArchiveTableOption", + "RdaTableOption": o.RdaTableOption, + "MigrationState": o.MigrationState, + "OptionKind": o.OptionKind, + } + if o.FilterPredicate != nil { + node["FilterPredicate"] = scalarExpressionToJSON(o.FilterPredicate) + } + return node + case *ast.RemoteDataArchiveAlterTableOption: + node := jsonNode{ + "$type": "RemoteDataArchiveAlterTableOption", + "RdaTableOption": o.RdaTableOption, + "MigrationState": o.MigrationState, + "IsMigrationStateSpecified": o.IsMigrationStateSpecified, + "IsFilterPredicateSpecified": o.IsFilterPredicateSpecified, + "OptionKind": o.OptionKind, + } + if o.FilterPredicate != nil { + node["FilterPredicate"] = scalarExpressionToJSON(o.FilterPredicate) + } + return node default: return jsonNode{"$type": "UnknownTableOption"} } @@ -6428,6 +7267,8 @@ func tableConstraintToJSON(c ast.TableConstraint) jsonNode { return foreignKeyConstraintToJSON(constraint) case *ast.GraphConnectionConstraintDefinition: return graphConnectionConstraintToJSON(constraint) + case *ast.DefaultConstraintDefinition: + return defaultConstraintToJSON(constraint) default: return jsonNode{"$type": "UnknownTableConstraint"} } @@ -6562,7 +7403,7 @@ func columnStorageOptionsToJSON(o *ast.ColumnStorageOptions) jsonNode { func defaultConstraintToJSON(d *ast.DefaultConstraintDefinition) jsonNode { node := jsonNode{ "$type": "DefaultConstraintDefinition", - "WithValues": false, + "WithValues": d.WithValues, } if d.ConstraintIdentifier != nil { node["ConstraintIdentifier"] = identifierToJSON(d.ConstraintIdentifier) @@ -6570,6 +7411,9 @@ func defaultConstraintToJSON(d *ast.DefaultConstraintDefinition) jsonNode { if d.Expression != nil { node["Expression"] = scalarExpressionToJSON(d.Expression) } + if d.Column != nil { + node["Column"] = identifierToJSON(d.Column) + } return node } @@ -6728,6 +7572,9 @@ func grantStatementToJSON(s *ast.GrantStatement) jsonNode { } node["Principals"] = principals } + if s.AsClause != nil { + node["AsClause"] = identifierToJSON(s.AsClause) + } return node } @@ -6793,6 +7640,13 @@ func securityTargetObjectToJSON(s *ast.SecurityTargetObject) jsonNode { if s.ObjectName != nil { node["ObjectName"] = securityTargetObjectNameToJSON(s.ObjectName) } + if len(s.Columns) > 0 { + cols := make([]jsonNode, len(s.Columns)) + for i, c := range s.Columns { + cols[i] = identifierToJSON(c) + } + node["Columns"] = cols + } return node } @@ -7520,6 +8374,110 @@ func alterServerRoleStatementToJSON(s *ast.AlterServerRoleStatement) jsonNode { return node } +func createAvailabilityGroupStatementToJSON(s *ast.CreateAvailabilityGroupStatement) jsonNode { + node := jsonNode{ + "$type": "CreateAvailabilityGroupStatement", + } + if s.Name != nil { + node["Name"] = identifierToJSON(s.Name) + } + if len(s.Options) > 0 { + opts := make([]jsonNode, len(s.Options)) + for i, opt := range s.Options { + opts[i] = availabilityGroupOptionToJSON(opt) + } + node["Options"] = opts + } + if len(s.Databases) > 0 { + dbs := make([]jsonNode, len(s.Databases)) + for i, db := range s.Databases { + dbs[i] = identifierToJSON(db) + } + node["Databases"] = dbs + } + if len(s.Replicas) > 0 { + reps := make([]jsonNode, len(s.Replicas)) + for i, rep := range s.Replicas { + reps[i] = availabilityReplicaToJSON(rep) + } + node["Replicas"] = reps + } + return node +} + +func availabilityGroupOptionToJSON(opt ast.AvailabilityGroupOption) jsonNode { + switch o := opt.(type) { + case *ast.LiteralAvailabilityGroupOption: + node := jsonNode{ + "$type": "LiteralAvailabilityGroupOption", + } + if o.Value != nil { + node["Value"] = scalarExpressionToJSON(o.Value) + } + node["OptionKind"] = o.OptionKind + return node + default: + return jsonNode{"$type": "UnknownAvailabilityGroupOption"} + } +} + +func availabilityReplicaToJSON(rep *ast.AvailabilityReplica) jsonNode { + node := jsonNode{ + "$type": "AvailabilityReplica", + } + if rep.ServerName != nil { + node["ServerName"] = stringLiteralToJSON(rep.ServerName) + } + if len(rep.Options) > 0 { + opts := make([]jsonNode, len(rep.Options)) + for i, opt := range rep.Options { + opts[i] = availabilityReplicaOptionToJSON(opt) + } + node["Options"] = opts + } + return node +} + +func availabilityReplicaOptionToJSON(opt ast.AvailabilityReplicaOption) jsonNode { + switch o := opt.(type) { + case *ast.AvailabilityModeReplicaOption: + return jsonNode{ + "$type": "AvailabilityModeReplicaOption", + "Value": o.Value, + "OptionKind": o.OptionKind, + } + case *ast.FailoverModeReplicaOption: + return jsonNode{ + "$type": "FailoverModeReplicaOption", + "Value": o.Value, + "OptionKind": o.OptionKind, + } + case *ast.LiteralReplicaOption: + node := jsonNode{ + "$type": "LiteralReplicaOption", + } + if o.Value != nil { + node["Value"] = scalarExpressionToJSON(o.Value) + } + node["OptionKind"] = o.OptionKind + return node + case *ast.PrimaryRoleReplicaOption: + return jsonNode{ + "$type": "PrimaryRoleReplicaOption", + "AllowConnections": o.AllowConnections, + "OptionKind": o.OptionKind, + } + case *ast.SecondaryRoleReplicaOption: + return jsonNode{ + "$type": "SecondaryRoleReplicaOption", + "AllowConnections": o.AllowConnections, + "OptionKind": o.OptionKind, + } + default: + return jsonNode{"$type": "UnknownReplicaOption"} + } +} + func createServerAuditStatementToJSON(s *ast.CreateServerAuditStatement) jsonNode { node := jsonNode{ "$type": "CreateServerAuditStatement", @@ -7543,31 +8501,138 @@ func createServerAuditStatementToJSON(s *ast.CreateServerAuditStatement) jsonNod return node } -func alterServerAuditStatementToJSON(s *ast.AlterServerAuditStatement) jsonNode { +func alterServerAuditStatementToJSON(s *ast.AlterServerAuditStatement) jsonNode { + node := jsonNode{ + "$type": "AlterServerAuditStatement", + "RemoveWhere": s.RemoveWhere, + } + if s.NewName != nil { + node["NewName"] = identifierToJSON(s.NewName) + } + if s.AuditName != nil { + node["AuditName"] = identifierToJSON(s.AuditName) + } + if s.AuditTarget != nil { + node["AuditTarget"] = auditTargetToJSON(s.AuditTarget) + } + if len(s.Options) > 0 { + options := make([]jsonNode, len(s.Options)) + for i, o := range s.Options { + options[i] = auditOptionToJSON(o) + } + node["Options"] = options + } + if s.PredicateExpression != nil { + node["PredicateExpression"] = booleanExpressionToJSON(s.PredicateExpression) + } + return node +} + +func createServerAuditSpecificationStatementToJSON(s *ast.CreateServerAuditSpecificationStatement) jsonNode { + node := jsonNode{ + "$type": "CreateServerAuditSpecificationStatement", + "AuditState": s.AuditState, + } + if len(s.Parts) > 0 { + parts := make([]jsonNode, len(s.Parts)) + for i, p := range s.Parts { + parts[i] = auditSpecificationPartToJSON(p) + } + node["Parts"] = parts + } + if s.SpecificationName != nil { + node["SpecificationName"] = identifierToJSON(s.SpecificationName) + } + if s.AuditName != nil { + node["AuditName"] = identifierToJSON(s.AuditName) + } + return node +} + +func alterServerAuditSpecificationStatementToJSON(s *ast.AlterServerAuditSpecificationStatement) jsonNode { + node := jsonNode{ + "$type": "AlterServerAuditSpecificationStatement", + "AuditState": s.AuditState, + } + if len(s.Parts) > 0 { + parts := make([]jsonNode, len(s.Parts)) + for i, p := range s.Parts { + parts[i] = auditSpecificationPartToJSON(p) + } + node["Parts"] = parts + } + if s.SpecificationName != nil { + node["SpecificationName"] = identifierToJSON(s.SpecificationName) + } + if s.AuditName != nil { + node["AuditName"] = identifierToJSON(s.AuditName) + } + return node +} + +func createDatabaseAuditSpecificationStatementToJSON(s *ast.CreateDatabaseAuditSpecificationStatement) jsonNode { + node := jsonNode{ + "$type": "CreateDatabaseAuditSpecificationStatement", + "AuditState": s.AuditState, + } + if len(s.Parts) > 0 { + parts := make([]jsonNode, len(s.Parts)) + for i, p := range s.Parts { + parts[i] = auditSpecificationPartToJSON(p) + } + node["Parts"] = parts + } + if s.SpecificationName != nil { + node["SpecificationName"] = identifierToJSON(s.SpecificationName) + } + if s.AuditName != nil { + node["AuditName"] = identifierToJSON(s.AuditName) + } + return node +} + +func alterDatabaseAuditSpecificationStatementToJSON(s *ast.AlterDatabaseAuditSpecificationStatement) jsonNode { + node := jsonNode{ + "$type": "AlterDatabaseAuditSpecificationStatement", + "AuditState": s.AuditState, + } + if len(s.Parts) > 0 { + parts := make([]jsonNode, len(s.Parts)) + for i, p := range s.Parts { + parts[i] = auditSpecificationPartToJSON(p) + } + node["Parts"] = parts + } + if s.SpecificationName != nil { + node["SpecificationName"] = identifierToJSON(s.SpecificationName) + } + if s.AuditName != nil { + node["AuditName"] = identifierToJSON(s.AuditName) + } + return node +} + +func auditSpecificationPartToJSON(p *ast.AuditSpecificationPart) jsonNode { node := jsonNode{ - "$type": "AlterServerAuditStatement", - "RemoveWhere": s.RemoveWhere, - } - if s.NewName != nil { - node["NewName"] = identifierToJSON(s.NewName) - } - if s.AuditName != nil { - node["AuditName"] = identifierToJSON(s.AuditName) + "$type": "AuditSpecificationPart", + "IsDrop": p.IsDrop, } - if s.AuditTarget != nil { - node["AuditTarget"] = auditTargetToJSON(s.AuditTarget) + if p.Details != nil { + node["Details"] = auditSpecificationDetailToJSON(p.Details) } - if len(s.Options) > 0 { - options := make([]jsonNode, len(s.Options)) - for i, o := range s.Options { - options[i] = auditOptionToJSON(o) + return node +} + +func auditSpecificationDetailToJSON(d ast.AuditSpecificationDetail) jsonNode { + switch detail := d.(type) { + case *ast.AuditActionGroupReference: + return jsonNode{ + "$type": "AuditActionGroupReference", + "Group": detail.Group, } - node["Options"] = options - } - if s.PredicateExpression != nil { - node["PredicateExpression"] = booleanExpressionToJSON(s.PredicateExpression) + default: + return jsonNode{} } - return node } func dropServerAuditStatementToJSON(s *ast.DropServerAuditStatement) jsonNode { @@ -7634,6 +8699,15 @@ func auditTargetOptionToJSON(o ast.AuditTargetOption) jsonNode { "Value": opt.Value, "OptionKind": opt.OptionKind, } + case *ast.RetentionDaysAuditTargetOption: + node := jsonNode{ + "$type": "RetentionDaysAuditTargetOption", + "OptionKind": opt.OptionKind, + } + if opt.Days != nil { + node["Days"] = scalarExpressionToJSON(opt.Days) + } + return node default: return jsonNode{"$type": "UnknownAuditTargetOption"} } @@ -9771,6 +10845,27 @@ func (p *Parser) parseCreateFunctionStatement() (*ast.CreateFunctionStatement, e param.DataType = dataType } + // Check for default value (= expr) + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + defaultValue, err := p.parseScalarExpression() + if err == nil { + param.Value = defaultValue + } + } + + // Check for NULL/NOT NULL nullability + if p.curTok.Type == TokenNull { + param.Nullable = &ast.NullableConstraintDefinition{Nullable: true} + p.nextToken() + } else if p.curTok.Type == TokenNot { + p.nextToken() // consume NOT + if p.curTok.Type == TokenNull { + param.Nullable = &ast.NullableConstraintDefinition{Nullable: false} + p.nextToken() + } + } + // Check for READONLY modifier if strings.ToUpper(p.curTok.Literal) == "READONLY" { param.Modifier = "ReadOnly" @@ -9798,7 +10893,17 @@ func (p *Parser) parseCreateFunctionStatement() (*ast.CreateFunctionStatement, e } p.nextToken() - // Check if RETURNS TABLE (table-valued function) + // Check if RETURNS @varName TABLE or RETURNS TABLE (table-valued function) + var tableVarName *ast.Identifier + if p.curTok.Type == TokenIdent && strings.HasPrefix(p.curTok.Literal, "@") { + // Parse variable name for multi-statement table-valued function + tableVarName = &ast.Identifier{ + Value: p.curTok.Literal, + QuoteType: "NotQuoted", + } + p.nextToken() + } + if strings.ToUpper(p.curTok.Literal) == "TABLE" { p.nextToken() @@ -9807,7 +10912,8 @@ func (p *Parser) parseCreateFunctionStatement() (*ast.CreateFunctionStatement, e p.nextToken() tableReturnType := &ast.TableValuedFunctionReturnType{ DeclareTableVariableBody: &ast.DeclareTableVariableBody{ - AsDefined: false, + VariableName: tableVarName, + AsDefined: false, Definition: &ast.TableDefinition{ ColumnDefinitions: []*ast.ColumnDefinition{}, }, @@ -9835,6 +10941,40 @@ func (p *Parser) parseCreateFunctionStatement() (*ast.CreateFunctionStatement, e colDef.DataType = dataType } + // Parse column constraints (PRIMARY KEY, NOT NULL, NULL, etc.) + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenComma && p.curTok.Type != TokenEOF { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "PRIMARY" { + p.nextToken() // consume PRIMARY + if strings.ToUpper(p.curTok.Literal) == "KEY" { + p.nextToken() // consume KEY + } + colDef.Constraints = append(colDef.Constraints, &ast.UniqueConstraintDefinition{ + IsPrimaryKey: true, + }) + } else if upperLit == "NOT" { + p.nextToken() // consume NOT + if p.curTok.Type == TokenNull { + p.nextToken() // consume NULL + colDef.Constraints = append(colDef.Constraints, &ast.NullableConstraintDefinition{ + Nullable: false, + }) + } + } else if p.curTok.Type == TokenNull { + p.nextToken() // consume NULL + colDef.Constraints = append(colDef.Constraints, &ast.NullableConstraintDefinition{ + Nullable: true, + }) + } else if upperLit == "UNIQUE" { + p.nextToken() // consume UNIQUE + colDef.Constraints = append(colDef.Constraints, &ast.UniqueConstraintDefinition{ + IsPrimaryKey: false, + }) + } else { + break + } + } + tableReturnType.DeclareTableVariableBody.Definition.ColumnDefinitions = append( tableReturnType.DeclareTableVariableBody.Definition.ColumnDefinitions, colDef, @@ -9948,6 +11088,14 @@ func (p *Parser) parseCreateFunctionStatement() (*ast.CreateFunctionStatement, e SelectStatement: sel, } } + } else { + // Multi-statement table-valued function: BEGIN ... END + stmtList, err := p.parseFunctionStatementList() + if err != nil { + p.skipToEndOfStatement() + return stmt, nil + } + stmt.StatementList = stmtList } } else { // Scalar function - parse return type @@ -10030,7 +11178,15 @@ func (p *Parser) parseFunctionOptions(stmt *ast.CreateFunctionStatement) { OptionState: state, }) case "ENCRYPTION", "SCHEMABINDING", "NATIVE_COMPILATION": - optKind := capitalizeFirst(strings.ToLower(p.curTok.Literal)) + var optKind string + switch upperOpt { + case "ENCRYPTION": + optKind = "Encryption" + case "SCHEMABINDING": + optKind = "SchemaBinding" + case "NATIVE_COMPILATION": + optKind = "NativeCompilation" + } p.nextToken() stmt.Options = append(stmt.Options, &ast.FunctionOption{ OptionKind: optKind, @@ -10149,6 +11305,27 @@ func (p *Parser) parseCreateOrAlterFunctionStatement() (*ast.CreateOrAlterFuncti param.DataType = dataType } + // Check for default value (= expr) + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + defaultValue, err := p.parseScalarExpression() + if err == nil { + param.Value = defaultValue + } + } + + // Check for NULL/NOT NULL nullability + if p.curTok.Type == TokenNull { + param.Nullable = &ast.NullableConstraintDefinition{Nullable: true} + p.nextToken() + } else if p.curTok.Type == TokenNot { + p.nextToken() // consume NOT + if p.curTok.Type == TokenNull { + param.Nullable = &ast.NullableConstraintDefinition{Nullable: false} + p.nextToken() + } + } + // Check for READONLY modifier if strings.ToUpper(p.curTok.Literal) == "READONLY" { param.Modifier = "ReadOnly" @@ -10209,14 +11386,23 @@ func (p *Parser) parseCreateOrAlterFunctionStatement() (*ast.CreateOrAlterFuncti OptionState: state, }) case "ENCRYPTION", "SCHEMABINDING", "NATIVE_COMPILATION", "CALLED": - optKind := capitalizeFirst(strings.ToLower(p.curTok.Literal)) + var optKind string + switch strings.ToUpper(p.curTok.Literal) { + case "ENCRYPTION": + optKind = "Encryption" + case "SCHEMABINDING": + optKind = "SchemaBinding" + case "NATIVE_COMPILATION": + optKind = "NativeCompilation" + case "CALLED": + optKind = "CalledOnNullInput" + } p.nextToken() - // Handle CALLED ON NULL INPUT - if optKind == "Called" { + // Handle CALLED ON NULL INPUT - skip additional tokens + if optKind == "CalledOnNullInput" { for strings.ToUpper(p.curTok.Literal) == "ON" || strings.ToUpper(p.curTok.Literal) == "NULL" || strings.ToUpper(p.curTok.Literal) == "INPUT" { p.nextToken() } - optKind = "CalledOnNullInput" } stmt.Options = append(stmt.Options, &ast.FunctionOption{ OptionKind: optKind, @@ -10229,6 +11415,45 @@ func (p *Parser) parseCreateOrAlterFunctionStatement() (*ast.CreateOrAlterFuncti stmt.Options = append(stmt.Options, &ast.FunctionOption{ OptionKind: "ReturnsNullOnNullInput", }) + case "EXECUTE": + p.nextToken() // consume EXECUTE + if p.curTok.Type == TokenAs { + p.nextToken() // consume AS + } + execAsOpt := &ast.ExecuteAsFunctionOption{ + OptionKind: "ExecuteAs", + ExecuteAs: &ast.ExecuteAsClause{}, + } + upperOption := strings.ToUpper(p.curTok.Literal) + switch upperOption { + case "CALLER": + execAsOpt.ExecuteAs.ExecuteAsOption = "Caller" + p.nextToken() + case "SELF": + execAsOpt.ExecuteAs.ExecuteAsOption = "Self" + p.nextToken() + case "OWNER": + execAsOpt.ExecuteAs.ExecuteAsOption = "Owner" + p.nextToken() + default: + // String literal for user name + if p.curTok.Type == TokenString { + execAsOpt.ExecuteAs.ExecuteAsOption = "String" + value := p.curTok.Literal + // Strip quotes + if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' { + value = value[1 : len(value)-1] + } + execAsOpt.ExecuteAs.Literal = &ast.StringLiteral{ + LiteralType: "String", + IsNational: false, + IsLargeObject: false, + Value: value, + } + p.nextToken() + } + } + stmt.Options = append(stmt.Options, execAsOpt) default: // Unknown option - skip it if p.curTok.Type == TokenIdent { @@ -10659,7 +11884,7 @@ func backupDatabaseStatementToJSON(s *ast.BackupDatabaseStatement) jsonNode { if len(s.Options) > 0 { options := make([]jsonNode, len(s.Options)) for i, o := range s.Options { - options[i] = backupOptionToJSON(o) + options[i] = backupOptionBaseToJSON(o) } node["Options"] = options } @@ -10690,7 +11915,7 @@ func backupTransactionLogStatementToJSON(s *ast.BackupTransactionLogStatement) j if len(s.Options) > 0 { options := make([]jsonNode, len(s.Options)) for i, o := range s.Options { - options[i] = backupOptionToJSON(o) + options[i] = backupOptionBaseToJSON(o) } node["Options"] = options } @@ -10784,6 +12009,17 @@ func restoreMasterKeyStatementToJSON(s *ast.RestoreMasterKeyStatement) jsonNode return node } +func backupOptionBaseToJSON(o ast.BackupOptionBase) jsonNode { + switch opt := o.(type) { + case *ast.BackupOption: + return backupOptionToJSON(opt) + case *ast.BackupEncryptionOption: + return backupEncryptionOptionToJSON(opt) + default: + return jsonNode{"$type": "Unknown"} + } +} + func backupOptionToJSON(o *ast.BackupOption) jsonNode { node := jsonNode{ "$type": "BackupOption", @@ -10795,6 +12031,18 @@ func backupOptionToJSON(o *ast.BackupOption) jsonNode { return node } +func backupEncryptionOptionToJSON(o *ast.BackupEncryptionOption) jsonNode { + node := jsonNode{ + "$type": "BackupEncryptionOption", + "Algorithm": o.Algorithm, + "OptionKind": o.OptionKind, + } + if o.Encryptor != nil { + node["Encryptor"] = cryptoMechanismToJSON(o.Encryptor) + } + return node +} + func deviceInfoToJSON(d *ast.DeviceInfo) jsonNode { node := jsonNode{ "$type": "DeviceInfo", @@ -12500,8 +13748,8 @@ func dropRoleStatementToJSON(s *ast.DropRoleStatement) jsonNode { func dropAssemblyStatementToJSON(s *ast.DropAssemblyStatement) jsonNode { node := jsonNode{ - "$type": "DropAssemblyStatement", - "IsIfExists": s.IsIfExists, + "$type": "DropAssemblyStatement", + "WithNoDependents": s.WithNoDependents, } if len(s.Objects) > 0 { objects := make([]jsonNode, len(s.Objects)) @@ -12510,6 +13758,7 @@ func dropAssemblyStatementToJSON(s *ast.DropAssemblyStatement) jsonNode { } node["Objects"] = objects } + node["IsIfExists"] = s.IsIfExists return node } @@ -12656,6 +13905,45 @@ func alterTableSetStatementToJSON(s *ast.AlterTableSetStatement) jsonNode { return node } +func alterTableRebuildStatementToJSON(s *ast.AlterTableRebuildStatement) jsonNode { + node := jsonNode{ + "$type": "AlterTableRebuildStatement", + } + if s.Partition != nil { + partNode := jsonNode{ + "$type": "PartitionSpecifier", + "All": s.Partition.All, + } + if s.Partition.Number != nil { + partNode["Number"] = scalarExpressionToJSON(s.Partition.Number) + } + node["Partition"] = partNode + } + if len(s.IndexOptions) > 0 { + var opts []jsonNode + for _, opt := range s.IndexOptions { + opts = append(opts, indexOptionToJSON(opt)) + } + node["IndexOptions"] = opts + } + if s.SchemaObjectName != nil { + node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName) + } + return node +} + +func alterTableChangeTrackingStatementToJSON(s *ast.AlterTableChangeTrackingModificationStatement) jsonNode { + node := jsonNode{ + "$type": "AlterTableChangeTrackingModificationStatement", + "IsEnable": s.IsEnable, + "TrackColumnsUpdated": s.TrackColumnsUpdated, + } + if s.SchemaObjectName != nil { + node["SchemaObjectName"] = schemaObjectNameToJSON(s.SchemaObjectName) + } + return node +} + func systemVersioningTableOptionToJSON(o *ast.SystemVersioningTableOption) jsonNode { node := jsonNode{ "$type": "SystemVersioningTableOption", @@ -12789,13 +14077,35 @@ func createExternalTableStatementToJSON(s *ast.CreateExternalTableStatement) jso if len(s.ExternalTableOptions) > 0 { opts := make([]jsonNode, len(s.ExternalTableOptions)) for i, opt := range s.ExternalTableOptions { - opts[i] = externalTableLiteralOrIdentifierOptionToJSON(opt) + opts[i] = externalTableOptionItemToJSON(opt) } node["ExternalTableOptions"] = opts } + if s.SelectStatement != nil { + node["SelectStatement"] = selectStatementToJSON(s.SelectStatement) + } return node } +func externalTableOptionItemToJSON(opt ast.ExternalTableOptionItem) jsonNode { + switch o := opt.(type) { + case *ast.ExternalTableLiteralOrIdentifierOption: + return externalTableLiteralOrIdentifierOptionToJSON(o) + case *ast.ExternalTableRejectTypeOption: + return externalTableRejectTypeOptionToJSON(o) + default: + return jsonNode{} + } +} + +func externalTableRejectTypeOptionToJSON(opt *ast.ExternalTableRejectTypeOption) jsonNode { + return jsonNode{ + "$type": "ExternalTableRejectTypeOption", + "Value": opt.Value, + "OptionKind": opt.OptionKind, + } +} + func externalTableColumnDefinitionToJSON(col *ast.ExternalTableColumnDefinition) jsonNode { node := jsonNode{ "$type": "ExternalTableColumnDefinition", @@ -13833,6 +15143,9 @@ func createDatabaseStatementToJSON(s *ast.CreateDatabaseStatement) jsonNode { if s.Collation != nil { node["Collation"] = identifierToJSON(s.Collation) } + if s.DatabaseSnapshot != nil { + node["DatabaseSnapshot"] = identifierToJSON(s.DatabaseSnapshot) + } return node } @@ -13914,6 +15227,11 @@ func fileDeclarationToJSON(fd *ast.FileDeclaration) jsonNode { func fileDeclarationOptionToJSON(opt ast.FileDeclarationOption) jsonNode { switch o := opt.(type) { + case *ast.SimpleFileDeclarationOption: + return jsonNode{ + "$type": "FileDeclarationOption", + "OptionKind": o.OptionKind, + } case *ast.NameFileDeclarationOption: node := jsonNode{ "$type": "NameFileDeclarationOption", @@ -14011,6 +15329,17 @@ func createDatabaseOptionToJSON(opt ast.CreateDatabaseOption) jsonNode { node["OptionKind"] = o.OptionKind } return node + case *ast.ElasticPoolSpecification: + node := jsonNode{ + "$type": "ElasticPoolSpecification", + } + if o.ElasticPoolName != nil { + node["ElasticPoolName"] = identifierToJSON(o.ElasticPoolName) + } + if o.OptionKind != "" { + node["OptionKind"] = o.OptionKind + } + return node case *ast.SimpleDatabaseOption: return jsonNode{ "$type": "DatabaseOption", @@ -14117,6 +15446,12 @@ func createIndexStatementToJSON(s *ast.CreateIndexStatement) jsonNode { } node["IndexOptions"] = opts } + if s.FilterPredicate != nil { + node["FilterPredicate"] = booleanExpressionToJSON(s.FilterPredicate) + } + if s.FileStreamOn != nil { + node["FileStreamOn"] = identifierOrValueExpressionToJSON(s.FileStreamOn) + } return node } @@ -14133,6 +15468,9 @@ func createAsymmetricKeyStatementToJSON(s *ast.CreateAsymmetricKeyStatement) jso if s.EncryptionAlgorithm != "" { node["EncryptionAlgorithm"] = s.EncryptionAlgorithm } + if s.Owner != nil { + node["Owner"] = identifierToJSON(s.Owner) + } if s.Password != nil { node["Password"] = scalarExpressionToJSON(s.Password) } @@ -14859,10 +16197,25 @@ func alterDatabaseModifyFileGroupStatementToJSON(s *ast.AlterDatabaseModifyFileG } else { node["UpdatabilityOption"] = "None" } + if s.Termination != nil { + node["Termination"] = alterDatabaseTerminationToJSON(s.Termination) + } if s.DatabaseName != nil { node["DatabaseName"] = identifierToJSON(s.DatabaseName) } - node["UseCurrent"] = false + node["UseCurrent"] = s.UseCurrent + return node +} + +func alterDatabaseTerminationToJSON(t *ast.AlterDatabaseTermination) jsonNode { + node := jsonNode{ + "$type": "AlterDatabaseTermination", + } + node["ImmediateRollback"] = t.ImmediateRollback + if t.RollbackAfter != nil { + node["RollbackAfter"] = scalarExpressionToJSON(t.RollbackAfter) + } + node["NoWait"] = t.NoWait return node } @@ -14922,6 +16275,20 @@ func alterDatabaseCollateStatementToJSON(s *ast.AlterDatabaseCollateStatement) j return node } +func alterDatabaseRebuildLogStatementToJSON(s *ast.AlterDatabaseRebuildLogStatement) jsonNode { + node := jsonNode{ + "$type": "AlterDatabaseRebuildLogStatement", + } + if s.FileDeclaration != nil { + node["FileDeclaration"] = fileDeclarationToJSON(s.FileDeclaration) + } + if s.DatabaseName != nil { + node["DatabaseName"] = identifierToJSON(s.DatabaseName) + } + node["UseCurrent"] = s.UseCurrent + return node +} + func alterDatabaseScopedConfigurationClearStatementToJSON(s *ast.AlterDatabaseScopedConfigurationClearStatement) jsonNode { node := jsonNode{ "$type": "AlterDatabaseScopedConfigurationClearStatement", @@ -14993,6 +16360,68 @@ func alterResourcePoolStatementToJSON(s *ast.AlterResourcePoolStatement) jsonNod return node } +func dropResourcePoolStatementToJSON(s *ast.DropResourcePoolStatement) jsonNode { + node := jsonNode{ + "$type": "DropResourcePoolStatement", + } + if s.Name != nil { + node["Name"] = identifierToJSON(s.Name) + } + node["IsIfExists"] = s.IsIfExists + return node +} + +func alterExternalResourcePoolStatementToJSON(s *ast.AlterExternalResourcePoolStatement) jsonNode { + node := jsonNode{ + "$type": "AlterExternalResourcePoolStatement", + } + if s.Name != nil { + node["Name"] = identifierToJSON(s.Name) + } + if len(s.ExternalResourcePoolParameters) > 0 { + params := make([]jsonNode, len(s.ExternalResourcePoolParameters)) + for i, param := range s.ExternalResourcePoolParameters { + params[i] = externalResourcePoolParameterToJSON(param) + } + node["ExternalResourcePoolParameters"] = params + } + return node +} + +func externalResourcePoolParameterToJSON(p *ast.ExternalResourcePoolParameter) jsonNode { + node := jsonNode{ + "$type": "ExternalResourcePoolParameter", + } + if p.ParameterType != "" { + node["ParameterType"] = p.ParameterType + } + if p.ParameterValue != nil { + node["ParameterValue"] = scalarExpressionToJSON(p.ParameterValue) + } + if p.AffinitySpecification != nil { + node["AffinitySpecification"] = externalResourcePoolAffinitySpecificationToJSON(p.AffinitySpecification) + } + return node +} + +func externalResourcePoolAffinitySpecificationToJSON(s *ast.ExternalResourcePoolAffinitySpecification) jsonNode { + node := jsonNode{ + "$type": "ExternalResourcePoolAffinitySpecification", + } + if s.AffinityType != "" { + node["AffinityType"] = s.AffinityType + } + node["IsAuto"] = s.IsAuto + if len(s.PoolAffinityRanges) > 0 { + ranges := make([]jsonNode, len(s.PoolAffinityRanges)) + for i, r := range s.PoolAffinityRanges { + ranges[i] = literalRangeToJSON(r) + } + node["PoolAffinityRanges"] = ranges + } + return node +} + func resourcePoolParameterToJSON(p *ast.ResourcePoolParameter) jsonNode { node := jsonNode{ "$type": "ResourcePoolParameter", @@ -15293,6 +16722,13 @@ func alterProcedureStatementToJSON(s *ast.AlterProcedureStatement) jsonNode { node["ProcedureReference"] = procedureReferenceToJSON(s.ProcedureReference) } node["IsForReplication"] = s.IsForReplication + if len(s.Options) > 0 { + opts := make([]jsonNode, len(s.Options)) + for i, o := range s.Options { + opts[i] = procedureOptionToJSON(o) + } + node["Options"] = opts + } if len(s.Parameters) > 0 { params := make([]jsonNode, len(s.Parameters)) for i, p := range s.Parameters { diff --git a/parser/parse_ddl.go b/parser/parse_ddl.go index 37b34561..e0da5aac 100644 --- a/parser/parse_ddl.go +++ b/parser/parse_ddl.go @@ -128,6 +128,8 @@ func (p *Parser) parseDropStatement() (ast.Statement, error) { return p.parseDropFulltextStatement() case "BROKER": return p.parseDropBrokerPriorityStatement() + case "RESOURCE": + return p.parseDropResourcePoolStatement() } return nil, fmt.Errorf("unexpected token after DROP: %s", p.curTok.Literal) @@ -405,6 +407,39 @@ func (p *Parser) parseDropExternalResourcePoolStatement() (*ast.DropExternalReso return stmt, nil } +func (p *Parser) parseDropResourcePoolStatement() (*ast.DropResourcePoolStatement, error) { + // Consume RESOURCE + p.nextToken() + + // Expect POOL + if strings.ToUpper(p.curTok.Literal) != "POOL" { + return nil, fmt.Errorf("expected POOL after RESOURCE, got %s", p.curTok.Literal) + } + p.nextToken() + + stmt := &ast.DropResourcePoolStatement{} + + // Check for IF EXISTS + if p.curTok.Type == TokenIf { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) != "EXISTS" { + return nil, fmt.Errorf("expected EXISTS after IF, got %s", p.curTok.Literal) + } + p.nextToken() + stmt.IsIfExists = true + } + + // Parse pool name + stmt.Name = p.parseIdentifier() + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + func (p *Parser) parseDropSecurityPolicyStatement() (*ast.DropSecurityPolicyStatement, error) { // Consume SECURITY p.nextToken() @@ -892,6 +927,16 @@ func (p *Parser) parseDropSequenceStatement() (*ast.DropSequenceStatement, error stmt := &ast.DropSequenceStatement{} + // Check for IF EXISTS + if strings.ToUpper(p.curTok.Literal) == "IF" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) != "EXISTS" { + return nil, fmt.Errorf("expected EXISTS after IF") + } + p.nextToken() + stmt.IsIfExists = true + } + // Parse comma-separated list of schema object names for { name, err := p.parseSchemaObjectName() @@ -1632,6 +1677,8 @@ func (p *Parser) parseAlterStatement() (ast.Statement, error) { return p.parseAlterCredentialStatement() case TokenExternal: return p.parseAlterExternalStatement() + case TokenView: + return p.parseAlterViewStatement() case TokenIdent: // Handle keywords that are not reserved tokens switch strings.ToUpper(p.curTok.Literal) { @@ -1754,6 +1801,15 @@ func (p *Parser) parseAlterDatabaseStatement() (ast.Statement, error) { // Consume DATABASE p.nextToken() + // Check for DATABASE AUDIT SPECIFICATION + if strings.ToUpper(p.curTok.Literal) == "AUDIT" { + p.nextToken() // consume AUDIT + if strings.ToUpper(p.curTok.Literal) == "SPECIFICATION" { + p.nextToken() // consume SPECIFICATION + return p.parseAlterDatabaseAuditSpecificationStatement() + } + } + // Check for DATABASE ENCRYPTION KEY if strings.ToUpper(p.curTok.Literal) == "ENCRYPTION" { return p.parseAlterDatabaseEncryptionKeyStatement() @@ -1803,6 +1859,9 @@ func (p *Parser) parseAlterDatabaseStatement() (ast.Statement, error) { if strings.ToUpper(p.curTok.Literal) == "REMOVE" { return p.parseAlterDatabaseRemoveStatement(dbName) } + if strings.ToUpper(p.curTok.Literal) == "REBUILD" { + return p.parseAlterDatabaseRebuildLogStatement(dbName) + } if strings.ToUpper(p.curTok.Literal) == "COLLATE" { p.nextToken() // consume COLLATE stmt := &ast.AlterDatabaseCollateStatement{ @@ -1905,6 +1964,56 @@ func (p *Parser) parseAlterDatabaseSetStatement(dbName *ast.Identifier) (*ast.Al p.nextToken() switch optionName { + // Simple database options without ON/OFF + case "ONLINE", "OFFLINE": + opt := &ast.SimpleDatabaseOption{OptionKind: capitalizeFirst(strings.ToLower(optionName))} + stmt.Options = append(stmt.Options, opt) + case "SINGLE_USER": + opt := &ast.SimpleDatabaseOption{OptionKind: "SingleUser"} + stmt.Options = append(stmt.Options, opt) + case "RESTRICTED_USER": + opt := &ast.SimpleDatabaseOption{OptionKind: "RestrictedUser"} + stmt.Options = append(stmt.Options, opt) + case "MULTI_USER": + opt := &ast.SimpleDatabaseOption{OptionKind: "MultiUser"} + stmt.Options = append(stmt.Options, opt) + case "READ_ONLY": + opt := &ast.SimpleDatabaseOption{OptionKind: "ReadOnly"} + stmt.Options = append(stmt.Options, opt) + case "READ_WRITE": + opt := &ast.SimpleDatabaseOption{OptionKind: "ReadWrite"} + stmt.Options = append(stmt.Options, opt) + case "RECOVERY": + // Expect FULL, BULK_LOGGED, or SIMPLE + recoveryType := strings.ToUpper(p.curTok.Literal) + p.nextToken() + recoveryValue := "Full" + switch recoveryType { + case "BULK_LOGGED": + recoveryValue = "BulkLogged" + case "SIMPLE": + recoveryValue = "Simple" + } + opt := &ast.RecoveryDatabaseOption{OptionKind: "Recovery", Value: recoveryValue} + stmt.Options = append(stmt.Options, opt) + case "CURSOR_CLOSE_ON_COMMIT": + // Expects ON/OFF + optionValue := strings.ToUpper(p.curTok.Literal) + p.nextToken() + opt := &ast.OnOffDatabaseOption{ + OptionKind: "CursorCloseOnCommit", + OptionState: capitalizeFirst(optionValue), + } + stmt.Options = append(stmt.Options, opt) + case "CURSOR_DEFAULT": + // Expects LOCAL or GLOBAL + cursorValue := strings.ToUpper(p.curTok.Literal) + p.nextToken() + opt := &ast.CursorDefaultDatabaseOption{ + OptionKind: "CursorDefault", + IsLocal: cursorValue == "LOCAL", + } + stmt.Options = append(stmt.Options, opt) case "ACCELERATED_DATABASE_RECOVERY": // Expect = for this option if p.curTok.Type != TokenEquals { @@ -1993,6 +2102,58 @@ func (p *Parser) parseAlterDatabaseSetStatement(dbName *ast.Identifier) (*ast.Al return nil, err } stmt.Options = append(stmt.Options, ctOpt) + case "EMERGENCY": + opt := &ast.GenericDatabaseOption{OptionKind: "Emergency"} + stmt.Options = append(stmt.Options, opt) + case "ERROR_BROKER_CONVERSATIONS": + opt := &ast.GenericDatabaseOption{OptionKind: "ErrorBrokerConversations"} + stmt.Options = append(stmt.Options, opt) + case "ENABLE_BROKER": + opt := &ast.GenericDatabaseOption{OptionKind: "EnableBroker"} + stmt.Options = append(stmt.Options, opt) + case "DISABLE_BROKER": + opt := &ast.GenericDatabaseOption{OptionKind: "DisableBroker"} + stmt.Options = append(stmt.Options, opt) + case "NEW_BROKER": + opt := &ast.GenericDatabaseOption{OptionKind: "NewBroker"} + stmt.Options = append(stmt.Options, opt) + case "PAGE_VERIFY": + // PAGE_VERIFY CHECKSUM | NONE | TORN_PAGE_DETECTION + verifyValue := strings.ToUpper(p.curTok.Literal) + p.nextToken() + value := "None" + switch verifyValue { + case "CHECKSUM": + value = "Checksum" + case "TORN_PAGE_DETECTION": + value = "TornPageDetection" + } + opt := &ast.PageVerifyDatabaseOption{ + OptionKind: "PageVerify", + Value: value, + } + stmt.Options = append(stmt.Options, opt) + case "PARTNER": + opt, err := p.parsePartnerDatabaseOption() + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, opt) + case "WITNESS": + opt, err := p.parseWitnessDatabaseOption() + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, opt) + case "PARAMETERIZATION": + // PARAMETERIZATION SIMPLE | FORCED + paramValue := strings.ToUpper(p.curTok.Literal) + p.nextToken() + opt := &ast.ParameterizationDatabaseOption{ + OptionKind: "Parameterization", + IsSimple: paramValue == "SIMPLE", + } + stmt.Options = append(stmt.Options, opt) default: // Handle generic options with = syntax (e.g., OPTIMIZED_LOCKING = ON) if p.curTok.Type == TokenEquals { @@ -2033,6 +2194,37 @@ func (p *Parser) parseAlterDatabaseSetStatement(dbName *ast.Identifier) (*ast.Al } } + // Parse optional termination clause: WITH NO_WAIT | WITH ROLLBACK AFTER N [SECONDS] | WITH ROLLBACK IMMEDIATE + if p.curTok.Type == TokenWith || strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + term := &ast.AlterDatabaseTermination{} + termKeyword := strings.ToUpper(p.curTok.Literal) + if termKeyword == "NO_WAIT" { + term.NoWait = true + p.nextToken() + } else if termKeyword == "ROLLBACK" { + p.nextToken() // consume ROLLBACK + rollbackType := strings.ToUpper(p.curTok.Literal) + if rollbackType == "AFTER" { + p.nextToken() // consume AFTER + // Parse the number + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + term.RollbackAfter = val + // Optional SECONDS keyword + if strings.ToUpper(p.curTok.Literal) == "SECONDS" { + p.nextToken() + } + } else if rollbackType == "IMMEDIATE" { + term.ImmediateRollback = true + p.nextToken() + } + } + stmt.Termination = term + } + // Skip optional semicolon if p.curTok.Type == TokenSemicolon { p.nextToken() @@ -2041,6 +2233,38 @@ func (p *Parser) parseAlterDatabaseSetStatement(dbName *ast.Identifier) (*ast.Al return stmt, nil } +// parseAlterDatabaseTermination parses the termination clause for ALTER DATABASE statements +// Forms: WITH NO_WAIT | WITH ROLLBACK AFTER N [SECONDS] | WITH ROLLBACK IMMEDIATE +func (p *Parser) parseAlterDatabaseTermination() *ast.AlterDatabaseTermination { + if p.curTok.Type != TokenWith && strings.ToUpper(p.curTok.Literal) != "WITH" { + return nil + } + p.nextToken() // consume WITH + term := &ast.AlterDatabaseTermination{} + termKeyword := strings.ToUpper(p.curTok.Literal) + if termKeyword == "NO_WAIT" { + term.NoWait = true + p.nextToken() + } else if termKeyword == "ROLLBACK" { + p.nextToken() // consume ROLLBACK + rollbackType := strings.ToUpper(p.curTok.Literal) + if rollbackType == "AFTER" { + p.nextToken() // consume AFTER + // Parse the number + val, _ := p.parseScalarExpression() + term.RollbackAfter = val + // Optional SECONDS keyword + if strings.ToUpper(p.curTok.Literal) == "SECONDS" { + p.nextToken() + } + } else if rollbackType == "IMMEDIATE" { + term.ImmediateRollback = true + p.nextToken() + } + } + return term +} + // parseRemoteDataArchiveOption parses REMOTE_DATA_ARCHIVE option // Forms: // - REMOTE_DATA_ARCHIVE = ON (options...) @@ -2205,6 +2429,87 @@ func (p *Parser) parseChangeTrackingOption() (*ast.ChangeTrackingDatabaseOption, return opt, nil } +// parsePartnerDatabaseOption parses PARTNER database mirroring option +func (p *Parser) parsePartnerDatabaseOption() (*ast.PartnerDatabaseOption, error) { + opt := &ast.PartnerDatabaseOption{ + OptionKind: "Partner", + } + + // Check if next token is = (PARTNER = 'server') + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + server, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + opt.PartnerServer = server + opt.PartnerOption = "PartnerServer" + return opt, nil + } + + // Otherwise, parse partner action + action := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + switch action { + case "FAILOVER": + opt.PartnerOption = "Failover" + case "FORCE_SERVICE_ALLOW_DATA_LOSS": + opt.PartnerOption = "ForceServiceAllowDataLoss" + case "RESUME": + opt.PartnerOption = "Resume" + case "SUSPEND": + opt.PartnerOption = "Suspend" + case "SAFETY": + // SAFETY FULL or SAFETY OFF + safetyVal := strings.ToUpper(p.curTok.Literal) + p.nextToken() + if safetyVal == "FULL" { + opt.PartnerOption = "SafetyFull" + } else { + opt.PartnerOption = "SafetyOff" + } + case "TIMEOUT": + // TIMEOUT value + opt.PartnerOption = "Timeout" + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + opt.Timeout = val + default: + opt.PartnerOption = capitalizeFirst(strings.ToLower(action)) + } + + return opt, nil +} + +// parseWitnessDatabaseOption parses WITNESS database mirroring option +func (p *Parser) parseWitnessDatabaseOption() (*ast.WitnessDatabaseOption, error) { + opt := &ast.WitnessDatabaseOption{ + OptionKind: "Witness", + } + + // Check if next token is = (WITNESS = 'server') + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + server, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + opt.WitnessServer = server + return opt, nil + } + + // Check for WITNESS OFF + if strings.ToUpper(p.curTok.Literal) == "OFF" { + opt.IsOff = true + p.nextToken() + } + + return opt, nil +} + func (p *Parser) parseAlterDatabaseAddStatement(dbName *ast.Identifier) (ast.Statement, error) { // Consume ADD p.nextToken() @@ -2247,6 +2552,14 @@ func (p *Parser) parseAlterDatabaseAddStatement(dbName *ast.Identifier) (ast.Sta return nil, err } stmt.FileDeclarations = decls + // Parse TO FILEGROUP + if strings.ToUpper(p.curTok.Literal) == "TO" { + p.nextToken() // consume TO + if strings.ToUpper(p.curTok.Literal) == "FILEGROUP" { + p.nextToken() // consume FILEGROUP + } + stmt.FileGroup = p.parseIdentifier() + } p.skipToEndOfStatement() return stmt, nil case "FILEGROUP": @@ -2325,6 +2638,7 @@ func (p *Parser) parseAlterDatabaseModifyStatement(dbName *ast.Identifier) (ast. FileGroupName: p.parseIdentifier(), } // Parse optional modifiers + filegroupLoop: for { switch strings.ToUpper(p.curTok.Literal) { case "DEFAULT": @@ -2355,10 +2669,15 @@ func (p *Parser) parseAlterDatabaseModifyStatement(dbName *ast.Identifier) (ast. } stmt.NewFileGroupName = p.parseIdentifier() default: - p.skipToEndOfStatement() - return stmt, nil + break filegroupLoop } } + // Parse optional WITH clause for termination + if p.curTok.Type == TokenWith { + stmt.Termination = p.parseAlterDatabaseTermination() + } + p.skipToEndOfStatement() + return stmt, nil case "NAME": p.nextToken() // consume NAME if p.curTok.Type == TokenEquals { @@ -2403,6 +2722,31 @@ func (p *Parser) parseAlterDatabaseRemoveStatement(dbName *ast.Identifier) (ast. } } +func (p *Parser) parseAlterDatabaseRebuildLogStatement(dbName *ast.Identifier) (*ast.AlterDatabaseRebuildLogStatement, error) { + p.nextToken() // consume REBUILD + + stmt := &ast.AlterDatabaseRebuildLogStatement{ + DatabaseName: dbName, + } + + // Expect LOG + if strings.ToUpper(p.curTok.Literal) == "LOG" { + p.nextToken() // consume LOG + } + + // Check for optional ON clause with file declaration + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + decls, _ := p.parseFileDeclarationList(false) + if len(decls) > 0 { + stmt.FileDeclaration = decls[0] + } + } + + p.skipToEndOfStatement() + return stmt, nil +} + func (p *Parser) parseAlterDatabaseScopedCredentialStatement() (*ast.AlterCredentialStatement, error) { // Consume CREDENTIAL p.nextToken() @@ -2858,12 +3202,16 @@ func (p *Parser) parseAlterTableStatement() (ast.Statement, error) { return p.parseAlterTableAddStatement(tableName) } - // Check for ENABLE/DISABLE TRIGGER or FILETABLE_NAMESPACE + // Check for ENABLE/DISABLE TRIGGER, FILETABLE_NAMESPACE, or CHANGE_TRACKING if strings.ToUpper(p.curTok.Literal) == "ENABLE" || strings.ToUpper(p.curTok.Literal) == "DISABLE" { // Check if it's FILETABLE_NAMESPACE if strings.ToUpper(p.peekTok.Literal) == "FILETABLE_NAMESPACE" { return p.parseAlterTableFileTableNamespaceStatement(tableName) } + // Check if it's CHANGE_TRACKING + if strings.ToUpper(p.peekTok.Literal) == "CHANGE_TRACKING" { + return p.parseAlterTableChangeTrackingStatement(tableName) + } return p.parseAlterTableTriggerModificationStatement(tableName) } @@ -2872,8 +3220,30 @@ func (p *Parser) parseAlterTableStatement() (ast.Statement, error) { return p.parseAlterTableSwitchStatement(tableName) } - // Check for WITH CHECK/NOCHECK or CHECK/NOCHECK CONSTRAINT - if strings.ToUpper(p.curTok.Literal) == "WITH" || strings.ToUpper(p.curTok.Literal) == "CHECK" || strings.ToUpper(p.curTok.Literal) == "NOCHECK" { + // Check for WITH CHECK/NOCHECK ADD - this is an add with enforcement option + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if strings.ToUpper(p.curTok.Literal) == "CHECK" || strings.ToUpper(p.curTok.Literal) == "NOCHECK" { + enforcement := "Check" + if strings.ToUpper(p.curTok.Literal) == "NOCHECK" { + enforcement = "NoCheck" + } + p.nextToken() // consume CHECK/NOCHECK + if strings.ToUpper(p.curTok.Literal) == "ADD" { + stmt, err := p.parseAlterTableAddStatement(tableName) + if err != nil { + return nil, err + } + stmt.ExistingRowsCheckEnforcement = enforcement + return stmt, nil + } + // It's a constraint modification statement (WITH CHECK/NOCHECK CONSTRAINT ...) + return p.parseAlterTableConstraintModificationStatementAfterWith(tableName, enforcement) + } + } + + // Check for CHECK/NOCHECK CONSTRAINT + if strings.ToUpper(p.curTok.Literal) == "CHECK" || strings.ToUpper(p.curTok.Literal) == "NOCHECK" { return p.parseAlterTableConstraintModificationStatement(tableName) } @@ -2882,6 +3252,11 @@ func (p *Parser) parseAlterTableStatement() (ast.Statement, error) { return p.parseAlterTableSetStatement(tableName) } + // Check for REBUILD + if strings.ToUpper(p.curTok.Literal) == "REBUILD" { + return p.parseAlterTableRebuildStatement(tableName) + } + return nil, fmt.Errorf("unexpected token in ALTER TABLE: %s", p.curTok.Literal) } @@ -2911,6 +3286,17 @@ func (p *Parser) parseAlterTableDropStatement(tableName *ast.SchemaObjectName) ( p.nextToken() } + // Check for IF EXISTS + var isIfExists bool + if strings.ToUpper(p.curTok.Literal) == "IF" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) != "EXISTS" { + return nil, fmt.Errorf("expected EXISTS after IF") + } + p.nextToken() + isIfExists = true + } + // Parse the element name if p.curTok.Type != TokenIdent && p.curTok.Type != TokenLBracket { if len(stmt.AlterTableDropTableElements) > 0 { @@ -2922,7 +3308,7 @@ func (p *Parser) parseAlterTableDropStatement(tableName *ast.SchemaObjectName) ( element := &ast.AlterTableDropTableElement{ TableElementType: currentElementType, Name: p.parseIdentifier(), - IsIfExists: false, + IsIfExists: isIfExists, } // Check for WITH clause @@ -3294,7 +3680,7 @@ func (p *Parser) parseAlterTableAlterColumnStatement(tableName *ast.SchemaObject // Parse column name stmt.ColumnIdentifier = p.parseIdentifier() - // Check for ADD/DROP ROWGUIDCOL or ADD/DROP NOT FOR REPLICATION or ADD/DROP PERSISTED + // Check for ADD/DROP ROWGUIDCOL or ADD/DROP NOT FOR REPLICATION or ADD/DROP PERSISTED or ADD/DROP SPARSE upperLit := strings.ToUpper(p.curTok.Literal) if upperLit == "ADD" { p.nextToken() // consume ADD @@ -3305,6 +3691,12 @@ func (p *Parser) parseAlterTableAlterColumnStatement(tableName *ast.SchemaObject } else if nextLit == "PERSISTED" { stmt.AlterTableAlterColumnOption = "AddPersisted" p.nextToken() + } else if nextLit == "SPARSE" { + stmt.AlterTableAlterColumnOption = "AddSparse" + p.nextToken() + } else if nextLit == "HIDDEN" { + stmt.AlterTableAlterColumnOption = "AddHidden" + p.nextToken() } else if nextLit == "NOT" { p.nextToken() // consume NOT if strings.ToUpper(p.curTok.Literal) == "FOR" { @@ -3329,6 +3721,12 @@ func (p *Parser) parseAlterTableAlterColumnStatement(tableName *ast.SchemaObject } else if nextLit == "PERSISTED" { stmt.AlterTableAlterColumnOption = "DropPersisted" p.nextToken() + } else if nextLit == "SPARSE" { + stmt.AlterTableAlterColumnOption = "DropSparse" + p.nextToken() + } else if nextLit == "HIDDEN" { + stmt.AlterTableAlterColumnOption = "DropHidden" + p.nextToken() } else if nextLit == "NOT" { p.nextToken() // consume NOT if strings.ToUpper(p.curTok.Literal) == "FOR" { @@ -3347,7 +3745,8 @@ func (p *Parser) parseAlterTableAlterColumnStatement(tableName *ast.SchemaObject } // Parse data type - be lenient if no data type is provided - dataType, err := p.parseDataType() + // Use parseDataTypeReference to handle all data types including XML + dataType, err := p.parseDataTypeReference() if err != nil { // Lenient: return statement without data type p.skipToEndOfStatement() @@ -3361,6 +3760,80 @@ func (p *Parser) parseAlterTableAlterColumnStatement(tableName *ast.SchemaObject stmt.Collation = p.parseIdentifier() } + // Parse optional SPARSE, FILESTREAM, COLUMN_SET FOR ALL_SPARSE_COLUMNS, HIDDEN, ENCRYPTED WITH, MASKED WITH + for { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "SPARSE" { + if stmt.StorageOptions == nil { + stmt.StorageOptions = &ast.ColumnStorageOptions{} + } + stmt.StorageOptions.SparseOption = "Sparse" + p.nextToken() + } else if upperLit == "FILESTREAM" { + if stmt.StorageOptions == nil { + stmt.StorageOptions = &ast.ColumnStorageOptions{} + } + stmt.StorageOptions.IsFileStream = true + p.nextToken() + } else if upperLit == "COLUMN_SET" { + p.nextToken() // consume COLUMN_SET + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + } + if strings.ToUpper(p.curTok.Literal) == "ALL_SPARSE_COLUMNS" { + p.nextToken() // consume ALL_SPARSE_COLUMNS + } + if stmt.StorageOptions == nil { + stmt.StorageOptions = &ast.ColumnStorageOptions{} + } + stmt.StorageOptions.SparseOption = "ColumnSetForAllSparseColumns" + } else if upperLit == "HIDDEN" { + stmt.IsHidden = true + p.nextToken() + } else if upperLit == "ENCRYPTED" { + p.nextToken() // consume ENCRYPTED + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + } + // Parse encryption specification: (COLUMN_ENCRYPTION_KEY = key1, ENCRYPTION_TYPE = ..., ALGORITHM = ...) + if p.curTok.Type == TokenLParen { + encSpec, err := p.parseColumnEncryptionSpecification() + if err != nil { + return nil, err + } + stmt.Encryption = encSpec + } + } else if upperLit == "MASKED" { + stmt.IsMasked = true + p.nextToken() // consume MASKED + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + } + // Parse masking function: (function = 'default()') + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + if strings.ToUpper(p.curTok.Literal) == "FUNCTION" { + p.nextToken() // consume FUNCTION + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + if p.curTok.Type == TokenString { + maskFunc, err := p.parseStringLiteral() + if err != nil { + return nil, err + } + stmt.MaskingFunction = maskFunc + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } else { + break + } + } + // Check for NULL/NOT NULL if strings.ToUpper(p.curTok.Literal) == "NULL" { stmt.AlterTableAlterColumnOption = "Null" @@ -3381,24 +3854,85 @@ func (p *Parser) parseAlterTableAlterColumnStatement(tableName *ast.SchemaObject return stmt, nil } -func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (*ast.AlterTableAddTableElementStatement, error) { - // Consume ADD - p.nextToken() - - stmt := &ast.AlterTableAddTableElementStatement{ - SchemaObjectName: tableName, - ExistingRowsCheckEnforcement: "NotSpecified", - Definition: &ast.TableDefinition{}, - } +func (p *Parser) parseColumnEncryptionSpecification() (*ast.ColumnEncryptionDefinition, error) { + // curTok should be ( + p.nextToken() // consume ( - // Check if this is ADD CONSTRAINT - if strings.ToUpper(p.curTok.Literal) == "CONSTRAINT" { - p.nextToken() // consume CONSTRAINT - // Parse constraint name - constraintName := p.parseIdentifier() + encDef := &ast.ColumnEncryptionDefinition{} - // Check what type of constraint follows - upperLit := strings.ToUpper(p.curTok.Literal) + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + paramName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + + switch paramName { + case "COLUMN_ENCRYPTION_KEY": + param := &ast.ColumnEncryptionKeyNameParameter{ + ParameterKind: "ColumnEncryptionKey", + Name: p.parseIdentifier(), + } + encDef.Parameters = append(encDef.Parameters, param) + case "ENCRYPTION_TYPE": + encType := strings.ToUpper(p.curTok.Literal) + param := &ast.ColumnEncryptionTypeParameter{ + ParameterKind: "EncryptionType", + } + if encType == "DETERMINISTIC" { + param.EncryptionType = "Deterministic" + } else if encType == "RANDOMIZED" { + param.EncryptionType = "Randomized" + } else { + param.EncryptionType = encType + } + p.nextToken() + encDef.Parameters = append(encDef.Parameters, param) + case "ALGORITHM": + str, err := p.parseStringLiteral() + if err != nil { + return nil, err + } + param := &ast.ColumnEncryptionAlgorithmParameter{ + ParameterKind: "Algorithm", + EncryptionAlgorithm: str, + } + encDef.Parameters = append(encDef.Parameters, param) + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + + return encDef, nil +} + +func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (*ast.AlterTableAddTableElementStatement, error) { + // Consume ADD + p.nextToken() + + stmt := &ast.AlterTableAddTableElementStatement{ + SchemaObjectName: tableName, + ExistingRowsCheckEnforcement: "NotSpecified", + Definition: &ast.TableDefinition{}, + } + + // Loop to parse multiple elements separated by commas + for { + // Check if this is ADD CONSTRAINT + if strings.ToUpper(p.curTok.Literal) == "CONSTRAINT" { + p.nextToken() // consume CONSTRAINT + // Parse constraint name + constraintName := p.parseIdentifier() + + // Check what type of constraint follows + upperLit := strings.ToUpper(p.curTok.Literal) switch upperLit { case "PRIMARY": @@ -3472,48 +4006,73 @@ func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (* p.nextToken() } } - // Parse WITH (index_options) + // Parse WITH (index_options) or WITH option = value (no parentheses) if p.curTok.Type == TokenWith { p.nextToken() // consume WITH - if p.curTok.Type == TokenLParen { + hasParen := p.curTok.Type == TokenLParen + if hasParen { p.nextToken() // consume ( - for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { - optionName := strings.ToUpper(p.curTok.Literal) - p.nextToken() - if p.curTok.Type == TokenEquals { - p.nextToken() // consume = + } + for { + // Check for end conditions + if hasParen && p.curTok.Type == TokenRParen { + break + } + if p.curTok.Type == TokenEOF || p.curTok.Type == TokenSemicolon { + break + } + // Check for ON (filegroup) which means we're done with options + if p.curTok.Type == TokenOn { + break + } + + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + // Check for ON/OFF state options + valueUpper := strings.ToUpper(p.curTok.Literal) + if valueUpper == "ON" || valueUpper == "OFF" || p.curTok.Type == TokenOn { + state := "On" + if valueUpper == "OFF" { + state = "Off" } - // Check for ON/OFF state options - valueUpper := strings.ToUpper(p.curTok.Literal) - if valueUpper == "ON" || valueUpper == "OFF" || p.curTok.Type == TokenOn { - state := "On" - if valueUpper == "OFF" { - state = "Off" - } - p.nextToken() // consume ON/OFF - option := &ast.IndexStateOption{ - OptionKind: convertIndexOptionKind(optionName), - OptionState: state, - } - constraint.IndexOptions = append(constraint.IndexOptions, option) - } else { - expr, _ := p.parseScalarExpression() - option := &ast.IndexExpressionOption{ - OptionKind: convertIndexOptionKind(optionName), - Expression: expr, - } - constraint.IndexOptions = append(constraint.IndexOptions, option) + p.nextToken() // consume ON/OFF + option := &ast.IndexStateOption{ + OptionKind: convertIndexOptionKind(optionName), + OptionState: state, } - if p.curTok.Type == TokenComma { - p.nextToken() - } else { - break + constraint.IndexOptions = append(constraint.IndexOptions, option) + } else { + expr, _ := p.parseScalarExpression() + option := &ast.IndexExpressionOption{ + OptionKind: convertIndexOptionKind(optionName), + Expression: expr, } + constraint.IndexOptions = append(constraint.IndexOptions, option) } - if p.curTok.Type == TokenRParen { + if p.curTok.Type == TokenComma { p.nextToken() + } else { + break } } + if hasParen && p.curTok.Type == TokenRParen { + p.nextToken() + } + } + // Parse ON filegroup + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + fgIdent := p.parseIdentifier() + fg := &ast.FileGroupOrPartitionScheme{ + Name: &ast.IdentifierOrValueExpression{ + Identifier: fgIdent, + Value: fgIdent.Value, + }, + } + constraint.OnFileGroupOrPartitionScheme = fg } // Parse NOT ENFORCED if strings.ToUpper(p.curTok.Literal) == "NOT" { @@ -3597,48 +4156,73 @@ func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (* p.nextToken() } } - // Parse WITH (index_options) + // Parse WITH (index_options) or WITH option = value (no parentheses) if p.curTok.Type == TokenWith { p.nextToken() // consume WITH - if p.curTok.Type == TokenLParen { + hasParen := p.curTok.Type == TokenLParen + if hasParen { p.nextToken() // consume ( - for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { - optionName := strings.ToUpper(p.curTok.Literal) - p.nextToken() - if p.curTok.Type == TokenEquals { - p.nextToken() // consume = + } + for { + // Check for end conditions + if hasParen && p.curTok.Type == TokenRParen { + break + } + if p.curTok.Type == TokenEOF || p.curTok.Type == TokenSemicolon { + break + } + // Check for ON (filegroup) which means we're done with options + if p.curTok.Type == TokenOn { + break + } + + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + // Check for ON/OFF state options + valueUpper := strings.ToUpper(p.curTok.Literal) + if valueUpper == "ON" || valueUpper == "OFF" || p.curTok.Type == TokenOn { + state := "On" + if valueUpper == "OFF" { + state = "Off" } - // Check for ON/OFF state options - valueUpper := strings.ToUpper(p.curTok.Literal) - if valueUpper == "ON" || valueUpper == "OFF" || p.curTok.Type == TokenOn { - state := "On" - if valueUpper == "OFF" { - state = "Off" - } - p.nextToken() // consume ON/OFF - option := &ast.IndexStateOption{ - OptionKind: convertIndexOptionKind(optionName), - OptionState: state, - } - constraint.IndexOptions = append(constraint.IndexOptions, option) - } else { - expr, _ := p.parseScalarExpression() - option := &ast.IndexExpressionOption{ - OptionKind: convertIndexOptionKind(optionName), - Expression: expr, - } - constraint.IndexOptions = append(constraint.IndexOptions, option) + p.nextToken() // consume ON/OFF + option := &ast.IndexStateOption{ + OptionKind: convertIndexOptionKind(optionName), + OptionState: state, } - if p.curTok.Type == TokenComma { - p.nextToken() - } else { - break + constraint.IndexOptions = append(constraint.IndexOptions, option) + } else { + expr, _ := p.parseScalarExpression() + option := &ast.IndexExpressionOption{ + OptionKind: convertIndexOptionKind(optionName), + Expression: expr, } + constraint.IndexOptions = append(constraint.IndexOptions, option) } - if p.curTok.Type == TokenRParen { + if p.curTok.Type == TokenComma { p.nextToken() + } else { + break } } + if hasParen && p.curTok.Type == TokenRParen { + p.nextToken() + } + } + // Parse ON filegroup + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + fgIdent := p.parseIdentifier() + fg := &ast.FileGroupOrPartitionScheme{ + Name: &ast.IdentifierOrValueExpression{ + Identifier: fgIdent, + Value: fgIdent.Value, + }, + } + constraint.OnFileGroupOrPartitionScheme = fg } // Parse NOT ENFORCED if strings.ToUpper(p.curTok.Literal) == "NOT" { @@ -3662,11 +4246,9 @@ func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (* constraint := &ast.ForeignKeyConstraintDefinition{ ConstraintIdentifier: constraintName, } - // Parse column list - track if we have a complete constraint - hasColumnsFK := false + // Parse optional column list hasReferences := false if p.curTok.Type == TokenLParen { - hasColumnsFK = true p.nextToken() // consume ( for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { ident := p.parseIdentifier() @@ -3707,6 +4289,52 @@ func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (* } } } + // Parse ON DELETE/ON UPDATE actions + for p.curTok.Type == TokenOn { + p.nextToken() // consume ON + actionType := strings.ToUpper(p.curTok.Literal) + if actionType == "DELETE" || actionType == "UPDATE" { + p.nextToken() // consume DELETE/UPDATE + action := "" + upperAction := strings.ToUpper(p.curTok.Literal) + if upperAction == "CASCADE" { + action = "Cascade" + p.nextToken() + } else if upperAction == "SET" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "NULL" { + action = "SetNull" + p.nextToken() + } else if strings.ToUpper(p.curTok.Literal) == "DEFAULT" { + action = "SetDefault" + p.nextToken() + } + } else if upperAction == "NO" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "ACTION" { + action = "NoAction" + p.nextToken() + } + } + if actionType == "DELETE" { + constraint.DeleteAction = action + } else { + constraint.UpdateAction = action + } + } else { + break + } + } + // Parse NOT FOR REPLICATION + if strings.ToUpper(p.curTok.Literal) == "NOT" && + strings.ToUpper(p.peekTok.Literal) == "FOR" { + p.nextToken() // consume NOT + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "REPLICATION" { + constraint.NotForReplication = true + p.nextToken() + } + } // Parse NOT ENFORCED if strings.ToUpper(p.curTok.Literal) == "NOT" { p.nextToken() @@ -3716,8 +4344,8 @@ func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (* constraint.IsEnforced = &f } } - // Only add constraint if we have columns and references - if hasColumnsFK && hasReferences { + // Only add constraint if we have references (columns are optional) + if hasReferences { stmt.Definition.TableConstraints = append(stmt.Definition.TableConstraints, constraint) } @@ -3760,15 +4388,57 @@ func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (* } stmt.Definition.TableConstraints = append(stmt.Definition.TableConstraints, constraint) + case "DEFAULT": + // CONSTRAINT name DEFAULT value FOR column [WITH VALUES] + p.nextToken() // consume DEFAULT + constraint := &ast.DefaultConstraintDefinition{ + ConstraintIdentifier: constraintName, + } + expr, err := p.parseScalarExpression() + if err != nil { + // Invalid/incomplete DEFAULT constraint - skip it + break + } + constraint.Expression = expr + // Check for FOR column + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + constraint.Column = p.parseIdentifier() + } + // Check for WITH VALUES + if p.curTok.Type == TokenWith && strings.ToUpper(p.peekTok.Literal) == "VALUES" { + p.nextToken() // consume WITH + p.nextToken() // consume VALUES + constraint.WithValues = true + } + stmt.Definition.TableConstraints = append(stmt.Definition.TableConstraints, constraint) + + case "CHECK": + // CONSTRAINT name CHECK (condition) + p.nextToken() // consume CHECK + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + expr, err := p.parseBooleanExpression() + if err != nil { + return nil, err + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + constraint := &ast.CheckConstraintDefinition{ + ConstraintIdentifier: constraintName, + CheckCondition: expr, + } + stmt.Definition.TableConstraints = append(stmt.Definition.TableConstraints, constraint) + } + default: // Unknown constraint type - skip to end of statement p.skipToEndOfStatement() } - return stmt, nil - } - - // Check if this is ADD INDEX - if p.curTok.Type == TokenIndex { + // Continue to check for comma (don't return here - allow multiple constraints) + } else if p.curTok.Type == TokenIndex { + // ADD INDEX p.nextToken() // consume INDEX indexDef := &ast.IndexDefinition{} @@ -3899,13 +4569,85 @@ func (p *Parser) parseAlterTableAddStatement(tableName *ast.SchemaObjectName) (* } stmt.Definition.Indexes = append(stmt.Definition.Indexes, indexDef) + } else if strings.ToUpper(p.curTok.Literal) == "CHECK" { + // Table-level CHECK constraint without CONSTRAINT keyword + p.nextToken() // consume CHECK + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + expr, err := p.parseBooleanExpression() + if err != nil { + p.skipToEndOfStatement() + } else { + if p.curTok.Type == TokenRParen { + p.nextToken() + } + constraint := &ast.CheckConstraintDefinition{ + CheckCondition: expr, + } + stmt.Definition.TableConstraints = append(stmt.Definition.TableConstraints, constraint) + } + } + } else { + // Parse column definition (column_name data_type ...) + colDef, err := p.parseColumnDefinition() + if err != nil { + return nil, err + } + stmt.Definition.ColumnDefinitions = append(stmt.Definition.ColumnDefinitions, colDef) + } + + // Check for comma to continue parsing more elements + if p.curTok.Type != TokenComma { + break + } + p.nextToken() // consume comma + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +func (p *Parser) parseAlterTableConstraintModificationStatementAfterWith(tableName *ast.SchemaObjectName, existingEnforcement string) (*ast.AlterTableConstraintModificationStatement, error) { + stmt := &ast.AlterTableConstraintModificationStatement{ + SchemaObjectName: tableName, + ExistingRowsCheckEnforcement: existingEnforcement, + } + + // Expect CHECK or NOCHECK + if strings.ToUpper(p.curTok.Literal) == "CHECK" { + stmt.ConstraintEnforcement = "Check" + p.nextToken() + } else if strings.ToUpper(p.curTok.Literal) == "NOCHECK" { + stmt.ConstraintEnforcement = "NoCheck" + p.nextToken() } else { - // Parse column definition (column_name data_type ...) - colDef, err := p.parseColumnDefinition() - if err != nil { - return nil, err + return nil, fmt.Errorf("expected CHECK or NOCHECK, got %s", p.curTok.Literal) + } + + // Expect CONSTRAINT + if strings.ToUpper(p.curTok.Literal) != "CONSTRAINT" { + return nil, fmt.Errorf("expected CONSTRAINT, got %s", p.curTok.Literal) + } + p.nextToken() + + // Check for ALL or constraint names + if strings.ToUpper(p.curTok.Literal) == "ALL" { + stmt.All = true + p.nextToken() + } else { + stmt.All = false + // Parse constraint names (comma-separated) + for { + stmt.ConstraintNames = append(stmt.ConstraintNames, p.parseIdentifier()) + if p.curTok.Type != TokenComma { + break + } + p.nextToken() // consume comma } - stmt.Definition.ColumnDefinitions = append(stmt.Definition.ColumnDefinitions, colDef) } // Skip optional semicolon @@ -4257,6 +4999,62 @@ func (p *Parser) parseAlterTableSetStatement(tableName *ast.SchemaObjectName) (* p.nextToken() } stmt.Options = append(stmt.Options, opt) + } else if optionName == "LOCK_ESCALATION" { + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + valueUpper := strings.ToUpper(p.curTok.Literal) + value := "Auto" + if valueUpper == "TABLE" { + value = "Table" + } else if valueUpper == "DISABLE" { + value = "Disable" + } + p.nextToken() + stmt.Options = append(stmt.Options, &ast.LockEscalationTableOption{ + OptionKind: "LockEscalation", + Value: value, + }) + } else if optionName == "FILESTREAM_ON" { + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + opt := &ast.FileStreamOnTableOption{ + OptionKind: "FileStreamOn", + } + if p.curTok.Type == TokenString { + value := p.curTok.Literal + if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' { + value = value[1 : len(value)-1] + } + opt.Value = &ast.IdentifierOrValueExpression{ + Value: value, + ValueExpression: &ast.StringLiteral{ + LiteralType: "String", + Value: value, + IsNational: false, + IsLargeObject: false, + }, + } + p.nextToken() + } else { + value := p.curTok.Literal + opt.Value = &ast.IdentifierOrValueExpression{ + Value: value, + Identifier: &ast.Identifier{ + Value: value, + QuoteType: "NotQuoted", + }, + } + p.nextToken() + } + stmt.Options = append(stmt.Options, opt) + } else if optionName == "REMOTE_DATA_ARCHIVE" { + rdaOpt, err := p.parseRemoteDataArchiveTableOption(true) + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, rdaOpt) } if p.curTok.Type == TokenComma { @@ -4424,34 +5222,117 @@ func (p *Parser) parseAlterRoleStatement() (*ast.AlterRoleStatement, error) { if strings.ToUpper(p.curTok.Literal) != "MEMBER" { return nil, fmt.Errorf("expected MEMBER after DROP, got %s", p.curTok.Literal) } - p.nextToken() // consume MEMBER - action := &ast.DropMemberAlterRoleAction{} - action.Member = p.parseIdentifier() - stmt.Action = action + p.nextToken() // consume MEMBER + action := &ast.DropMemberAlterRoleAction{} + action.Member = p.parseIdentifier() + stmt.Action = action + + case "WITH": + p.nextToken() // consume WITH + if strings.ToUpper(p.curTok.Literal) != "NAME" { + return nil, fmt.Errorf("expected NAME after WITH, got %s", p.curTok.Literal) + } + p.nextToken() // consume NAME + if p.curTok.Type != TokenEquals { + return nil, fmt.Errorf("expected = after NAME, got %s", p.curTok.Literal) + } + p.nextToken() // consume = + action := &ast.RenameAlterRoleAction{} + action.NewName = p.parseIdentifier() + stmt.Action = action + + default: + // Handle incomplete statement + p.skipToEndOfStatement() + return stmt, nil + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +func (p *Parser) parseAlterViewStatement() (*ast.AlterViewStatement, error) { + // Consume VIEW + p.nextToken() + + stmt := &ast.AlterViewStatement{} + + // Parse view name + son, err := p.parseSchemaObjectName() + if err != nil { + return nil, err + } + stmt.SchemaObjectName = son + + // Check for column list + if p.curTok.Type == TokenLParen { + p.nextToken() + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + stmt.Columns = append(stmt.Columns, p.parseIdentifier()) + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + + // Check for WITH options + if p.curTok.Type == TokenWith { + p.nextToken() + // Parse view options (can be identifiers or keywords like ENCRYPTION) + for p.curTok.Type != TokenAs && p.curTok.Type != TokenEOF && p.curTok.Type != TokenSemicolon { + optName := strings.ToUpper(p.curTok.Literal) + var optionKind string + switch optName { + case "ENCRYPTION": + optionKind = "Encryption" + case "SCHEMABINDING": + optionKind = "SchemaBinding" + case "VIEW_METADATA": + optionKind = "ViewMetadata" + default: + optionKind = p.curTok.Literal + } + opt := &ast.ViewStatementOption{OptionKind: optionKind} + stmt.ViewOptions = append(stmt.ViewOptions, opt) + p.nextToken() + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + } - case "WITH": - p.nextToken() // consume WITH - if strings.ToUpper(p.curTok.Literal) != "NAME" { - return nil, fmt.Errorf("expected NAME after WITH, got %s", p.curTok.Literal) - } - p.nextToken() // consume NAME - if p.curTok.Type != TokenEquals { - return nil, fmt.Errorf("expected = after NAME, got %s", p.curTok.Literal) - } - p.nextToken() // consume = - action := &ast.RenameAlterRoleAction{} - action.NewName = p.parseIdentifier() - stmt.Action = action + // Expect AS + if p.curTok.Type != TokenAs { + p.skipToEndOfStatement() + return stmt, nil + } + p.nextToken() - default: - // Handle incomplete statement + // Parse SELECT statement + selStmt, err := p.parseSelectStatement() + if err != nil { p.skipToEndOfStatement() return stmt, nil } + stmt.SelectStatement = selStmt - // Skip optional semicolon - if p.curTok.Type == TokenSemicolon { + // Check for WITH CHECK OPTION + if p.curTok.Type == TokenWith { p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "CHECK" { + p.nextToken() + if p.curTok.Type == TokenOption { + p.nextToken() + stmt.WithCheckOption = true + } + } } return stmt, nil @@ -4516,10 +5397,15 @@ func (p *Parser) parseAlterServerRoleStatement() (*ast.AlterServerRoleStatement, return stmt, nil } -func (p *Parser) parseAlterServerAuditStatement() (*ast.AlterServerAuditStatement, error) { +func (p *Parser) parseAlterServerAuditStatement() (ast.Statement, error) { // AUDIT keyword should be current token, consume it p.nextToken() + // Check if this is ALTER SERVER AUDIT SPECIFICATION + if strings.ToUpper(p.curTok.Literal) == "SPECIFICATION" { + return p.parseAlterServerAuditSpecificationStatement() + } + stmt := &ast.AlterServerAuditStatement{} // Parse audit name @@ -6631,20 +7517,70 @@ func (p *Parser) parseAlterProcedureStatement() (*ast.AlterProcedureStatement, e stmt.Parameters = params } - // Skip WITH options (like RECOMPILE, ENCRYPTION, etc.) + // Parse WITH options (like RECOMPILE, ENCRYPTION, NATIVE_COMPILATION, etc.) if p.curTok.Type == TokenWith { p.nextToken() for { if strings.ToUpper(p.curTok.Literal) == "FOR" || p.curTok.Type == TokenAs || p.curTok.Type == TokenEOF { break } - if strings.ToUpper(p.curTok.Literal) == "REPLICATION" { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "RECOMPILE" { + stmt.Options = append(stmt.Options, &ast.ProcedureOption{OptionKind: "Recompile"}) + p.nextToken() + } else if upperLit == "ENCRYPTION" { + stmt.Options = append(stmt.Options, &ast.ProcedureOption{OptionKind: "Encryption"}) + p.nextToken() + } else if upperLit == "NATIVE_COMPILATION" { + stmt.Options = append(stmt.Options, &ast.ProcedureOption{OptionKind: "NativeCompilation"}) + p.nextToken() + } else if upperLit == "SCHEMABINDING" { + stmt.Options = append(stmt.Options, &ast.ProcedureOption{OptionKind: "SchemaBinding"}) + p.nextToken() + } else if upperLit == "EXECUTE" { + p.nextToken() // consume EXECUTE + if p.curTok.Type == TokenAs { + p.nextToken() // consume AS + } + executeAsOpt := &ast.ExecuteAsProcedureOption{ + OptionKind: "ExecuteAs", + ExecuteAs: &ast.ExecuteAsClause{}, + } + upperOption := strings.ToUpper(p.curTok.Literal) + if upperOption == "CALLER" { + executeAsOpt.ExecuteAs.ExecuteAsOption = "Caller" + p.nextToken() + } else if upperOption == "SELF" { + executeAsOpt.ExecuteAs.ExecuteAsOption = "Self" + p.nextToken() + } else if upperOption == "OWNER" { + executeAsOpt.ExecuteAs.ExecuteAsOption = "Owner" + p.nextToken() + } else if p.curTok.Type == TokenString { + executeAsOpt.ExecuteAs.ExecuteAsOption = "String" + value := p.curTok.Literal + // Strip quotes + if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' { + value = value[1 : len(value)-1] + } + executeAsOpt.ExecuteAs.Literal = &ast.StringLiteral{ + LiteralType: "String", + IsNational: false, + IsLargeObject: false, + Value: value, + } + p.nextToken() + } + stmt.Options = append(stmt.Options, executeAsOpt) + } else if upperLit == "REPLICATION" { stmt.IsForReplication = true + p.nextToken() + } else { + p.nextToken() } - p.nextToken() if p.curTok.Type == TokenComma { p.nextToken() - } else { + } else if p.curTok.Type == TokenAs || strings.ToUpper(p.curTok.Literal) == "FOR" || p.curTok.Type == TokenEOF { break } } @@ -6678,6 +7614,8 @@ func (p *Parser) parseAlterExternalStatement() (ast.Statement, error) { return p.parseAlterExternalLanguageStatement() case "LIBRARY": return p.parseAlterExternalLibraryStatement() + case "RESOURCE": + return p.parseAlterExternalResourcePoolStatement() default: // Skip to end of statement for unsupported external statements p.skipToEndOfStatement() @@ -6937,12 +7875,176 @@ func (p *Parser) parseAlterExternalLibraryStatement() (*ast.AlterExternalLibrary return stmt, nil } +func (p *Parser) parseAlterExternalResourcePoolStatement() (*ast.AlterExternalResourcePoolStatement, error) { + // Consume RESOURCE + p.nextToken() + + // Expect POOL + if strings.ToUpper(p.curTok.Literal) != "POOL" { + return nil, fmt.Errorf("expected POOL, got %s", p.curTok.Literal) + } + p.nextToken() + + stmt := &ast.AlterExternalResourcePoolStatement{} + + // Parse pool name + stmt.Name = p.parseIdentifier() + + // Expect WITH + if p.curTok.Type != TokenWith && strings.ToUpper(p.curTok.Literal) != "WITH" { + return nil, fmt.Errorf("expected WITH, got %s", p.curTok.Literal) + } + p.nextToken() + + // Expect ( + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected (, got %s", p.curTok.Literal) + } + p.nextToken() + + // Parse parameters + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + paramName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + param := &ast.ExternalResourcePoolParameter{} + + switch paramName { + case "MAX_CPU_PERCENT": + param.ParameterType = "MaxCpuPercent" + if p.curTok.Type == TokenEquals { + p.nextToken() + } + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + param.ParameterValue = val + case "MAX_MEMORY_PERCENT": + param.ParameterType = "MaxMemoryPercent" + if p.curTok.Type == TokenEquals { + p.nextToken() + } + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + param.ParameterValue = val + case "MAX_PROCESSES": + param.ParameterType = "MaxProcesses" + if p.curTok.Type == TokenEquals { + p.nextToken() + } + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + param.ParameterValue = val + case "AFFINITY": + param.ParameterType = "Affinity" + affinitySpec := &ast.ExternalResourcePoolAffinitySpecification{} + + // Parse CPU or NUMANODE + affinityType := strings.ToUpper(p.curTok.Literal) + p.nextToken() + if affinityType == "CPU" { + affinitySpec.AffinityType = "Cpu" + } else if affinityType == "NUMANODE" { + affinitySpec.AffinityType = "NumaNode" + } + + // Expect = + if p.curTok.Type == TokenEquals { + p.nextToken() + } + + // Check for AUTO or range list + if strings.ToUpper(p.curTok.Literal) == "AUTO" { + affinitySpec.IsAuto = true + p.nextToken() + } else { + // Parse range list: (1) or (1 TO 5, 6 TO 7) + if p.curTok.Type == TokenLParen { + p.nextToken() + } + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + fromVal, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + rangeItem := &ast.LiteralRange{From: fromVal} + + // Check for TO + if strings.ToUpper(p.curTok.Literal) == "TO" { + p.nextToken() + toVal, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + rangeItem.To = toVal + } + + affinitySpec.PoolAffinityRanges = append(affinitySpec.PoolAffinityRanges, rangeItem) + + // Check for comma + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + param.AffinitySpecification = affinitySpec + } + + stmt.ExternalResourcePoolParameters = append(stmt.ExternalResourcePoolParameters, param) + + // Check for comma + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + + // Consume ) + if p.curTok.Type == TokenRParen { + p.nextToken() + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + // convertOptionKind converts a SQL option name (e.g., "OPTIMIZED_LOCKING") to its OptionKind form (e.g., "OptimizedLocking") func convertOptionKind(optionName string) string { // Handle special cases with specific capitalization switch optionName { case "VARDECIMAL_STORAGE_FORMAT": return "VarDecimalStorageFormat" + case "ARITHABORT": + return "ArithAbort" + case "NUMERIC_ROUNDABORT": + return "NumericRoundAbort" + case "DB_CHAINING": + return "DBChaining" + case "AUTO_UPDATE_STATISTICS_ASYNC": + return "AutoUpdateStatisticsAsync" + case "DATE_CORRELATION_OPTIMIZATION": + return "DateCorrelationOptimization" + case "ALLOW_SNAPSHOT_ISOLATION": + return "AllowSnapshotIsolation" + case "READ_COMMITTED_SNAPSHOT": + return "ReadCommittedSnapshot" + case "SUPPLEMENTAL_LOGGING": + return "SupplementalLogging" } // Split by underscores and capitalize each word @@ -8022,3 +9124,132 @@ func (p *Parser) parseDropBrokerPriorityStatement() (*ast.DropBrokerPriorityStat p.skipToEndOfStatement() return stmt, nil } + +func (p *Parser) parseAlterTableRebuildStatement(tableName *ast.SchemaObjectName) (*ast.AlterTableRebuildStatement, error) { + stmt := &ast.AlterTableRebuildStatement{ + SchemaObjectName: tableName, + } + + // Consume REBUILD + p.nextToken() + + // Check for PARTITION + if strings.ToUpper(p.curTok.Literal) == "PARTITION" { + p.nextToken() // consume PARTITION + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + stmt.Partition = &ast.PartitionSpecifier{} + if strings.ToUpper(p.curTok.Literal) == "ALL" { + stmt.Partition.All = true + p.nextToken() + } else if p.curTok.Type == TokenNumber { + stmt.Partition.All = false + stmt.Partition.Number = &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + } + p.nextToken() + } + } + + // Check for WITH + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume option name + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + switch optionName { + case "MAXDOP": + opt := &ast.IndexExpressionOption{ + OptionKind: "MaxDop", + Expression: &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + }, + } + stmt.IndexOptions = append(stmt.IndexOptions, opt) + p.nextToken() + case "SORT_IN_TEMPDB": + stateUpper := strings.ToUpper(p.curTok.Literal) + state := "On" + if stateUpper == "OFF" { + state = "Off" + } + p.nextToken() + opt := &ast.IndexStateOption{ + OptionKind: "SortInTempDB", + OptionState: state, + } + stmt.IndexOptions = append(stmt.IndexOptions, opt) + default: + // Skip unknown options + p.nextToken() + } + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + } + + return stmt, nil +} + +func (p *Parser) parseAlterTableChangeTrackingStatement(tableName *ast.SchemaObjectName) (*ast.AlterTableChangeTrackingModificationStatement, error) { + stmt := &ast.AlterTableChangeTrackingModificationStatement{ + SchemaObjectName: tableName, + TrackColumnsUpdated: "NotSet", + } + + // Parse ENABLE or DISABLE + if strings.ToUpper(p.curTok.Literal) == "ENABLE" { + stmt.IsEnable = true + } + p.nextToken() // consume ENABLE/DISABLE + + // Consume CHANGE_TRACKING + p.nextToken() + + // Check for WITH + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optionName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + if p.curTok.Type == TokenEquals { + p.nextToken() + } + if optionName == "TRACK_COLUMNS_UPDATED" { + valueUpper := strings.ToUpper(p.curTok.Literal) + if valueUpper == "ON" { + stmt.TrackColumnsUpdated = "On" + } else if valueUpper == "OFF" { + stmt.TrackColumnsUpdated = "Off" + } + p.nextToken() + } else { + p.nextToken() + } + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + } + + return stmt, nil +} diff --git a/parser/parse_select.go b/parser/parse_select.go index 41328e31..a9c8d4bc 100644 --- a/parser/parse_select.go +++ b/parser/parse_select.go @@ -82,6 +82,27 @@ func (p *Parser) parseThrowStatement() (*ast.ThrowStatement, error) { func (p *Parser) parseSelectStatement() (*ast.SelectStatement, error) { stmt := &ast.SelectStatement{} + // Check for parenthesized WITH expression: (WITH ... SELECT ...) + // Only handle this case specially, let normal parsing handle other cases + if p.curTok.Type == TokenLParen && p.peekTok.Type == TokenWith { + p.nextToken() // consume ( + // Parse WITH clause and SELECT statement + withStmt, err := p.parseWithStatement() + if err != nil { + return nil, err + } + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ), got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + + // Return the SelectStatement with its WithCtesAndXmlNamespaces + if selStmt, ok := withStmt.(*ast.SelectStatement); ok { + return selStmt, nil + } + return nil, fmt.Errorf("expected SELECT statement after WITH, got %T", withStmt) + } + // Parse query expression (handles UNION, parens, etc.) qe, into, on, err := p.parseQueryExpressionWithInto() if err != nil { @@ -520,6 +541,51 @@ func (p *Parser) parseSelectElement() (ast.SelectElement, error) { return sse, nil } + // Check for equals-style alias: column1 = expr, [column1] = expr, 'string' = expr, N'string' = expr + // This is detected by seeing identifier or string followed by = + if p.peekTok.Type == TokenEquals { + // We have alias = expr pattern + var columnName *ast.IdentifierOrValueExpression + + if p.curTok.Type == TokenIdent { + // identifier = expr + alias := p.parseIdentifier() + columnName = &ast.IdentifierOrValueExpression{ + Value: alias.Value, + Identifier: alias, + } + } else if p.curTok.Type == TokenString { + // 'string' = expr + str := p.parseStringLiteralValue() + p.nextToken() + columnName = &ast.IdentifierOrValueExpression{ + Value: str.Value, + ValueExpression: str, + } + } else if p.curTok.Type == TokenNationalString { + // N'string' = expr + str, _ := p.parseNationalStringFromToken() + columnName = &ast.IdentifierOrValueExpression{ + Value: str.Value, + ValueExpression: str, + } + } + + if columnName != nil { + p.nextToken() // consume = + + expr, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + + return &ast.SelectScalarExpression{ + Expression: expr, + ColumnName: columnName, + }, nil + } + } + // Otherwise parse a scalar expression expr, err := p.parseScalarExpression() if err != nil { @@ -586,6 +652,21 @@ func (p *Parser) parseSelectElement() (ast.SelectElement, error) { Identifier: alias, } } + } else if p.curTok.Type == TokenString { + // String literal alias without AS: expr 'alias' + str := p.parseStringLiteralValue() + p.nextToken() + sse.ColumnName = &ast.IdentifierOrValueExpression{ + Value: str.Value, + ValueExpression: str, + } + } else if p.curTok.Type == TokenNationalString { + // National string literal alias without AS: expr N'alias' + str, _ := p.parseNationalStringFromToken() + sse.ColumnName = &ast.IdentifierOrValueExpression{ + Value: str.Value, + ValueExpression: str, + } } else if p.curTok.Type == TokenIdent { // Check if this is an alias (not a keyword that starts a new clause) upper := strings.ToUpper(p.curTok.Literal) @@ -1086,6 +1167,22 @@ func (p *Parser) parsePrimaryExpression() (ast.ScalarExpression, error) { p.nextToken() return &ast.ColumnReferenceExpression{ColumnType: "RowGuidCol"}, nil } + if upper == "$ACTION" { + p.nextToken() + return &ast.ColumnReferenceExpression{ColumnType: "PseudoColumnAction"}, nil + } + if upper == "$IDENTITY" { + p.nextToken() + return &ast.ColumnReferenceExpression{ColumnType: "PseudoColumnIdentity"}, nil + } + if upper == "$ROWGUID" { + p.nextToken() + return &ast.ColumnReferenceExpression{ColumnType: "PseudoColumnRowGuid"}, nil + } + if upper == "$CUID" { + p.nextToken() + return &ast.ColumnReferenceExpression{ColumnType: "PseudoColumnCuid"}, nil + } // Check for NEXT VALUE FOR sequence expression if upper == "NEXT" && strings.ToUpper(p.peekTok.Literal) == "VALUE" { return p.parseNextValueForExpression() @@ -1531,6 +1628,14 @@ func (p *Parser) parseColumnReferenceOrFunctionCall() (ast.ScalarExpression, err if len(literal) >= 2 && literal[0] == '[' && literal[len(literal)-1] == ']' { quoteType = "SquareBracket" literal = literal[1 : len(literal)-1] + // Unescape ]] to ] + literal = strings.ReplaceAll(literal, "]]", "]") + } else if len(literal) >= 2 && literal[0] == '"' && literal[len(literal)-1] == '"' { + // Handle double-quoted identifiers + quoteType = "DoubleQuote" + literal = literal[1 : len(literal)-1] + // Unescape "" to " + literal = strings.ReplaceAll(literal, "\"\"", "\"") } else if upper == "IDENTITYCOL" || upper == "ROWGUIDCOL" { // IDENTITYCOL/ROWGUIDCOL at end of multi-part identifier sets column type // and is not included in the identifier list @@ -1892,6 +1997,19 @@ func (p *Parser) parseColumnReferenceWithLeadingDots() (ast.ScalarExpression, er } func (p *Parser) parseFunctionCallFromIdentifiers(identifiers []*ast.Identifier) (ast.ScalarExpression, error) { + // Check for special functions that need custom handling + if len(identifiers) == 1 { + funcName := strings.ToUpper(identifiers[0].Value) + switch funcName { + case "IIF": + return p.parseIIfCall() + case "PARSE": + return p.parseParseCall(false) + case "TRY_PARSE": + return p.parseParseCall(true) + } + } + fc := &ast.FunctionCall{ UniqueRowFilter: "NotSpecified", WithArrayWrapper: false, @@ -2435,7 +2553,7 @@ func (p *Parser) parseNamedTableReference() (*ast.NamedTableReference, error) { } // Parse optional table hints WITH (hint, hint, ...) or old-style (hint, hint, ...) - if p.curTok.Type == TokenWith { + if p.curTok.Type == TokenWith && p.peekTok.Type == TokenLParen { p.nextToken() // consume WITH } if p.curTok.Type == TokenLParen { @@ -2497,7 +2615,7 @@ func (p *Parser) parseNamedTableReferenceWithName(son *ast.SchemaObjectName) (*a } // Parse optional table hints WITH (hint, hint, ...) or old-style (hint, hint, ...) - if p.curTok.Type == TokenWith { + if p.curTok.Type == TokenWith && p.peekTok.Type == TokenLParen { p.nextToken() // consume WITH } if p.curTok.Type == TokenLParen { @@ -4068,6 +4186,17 @@ func (p *Parser) parseBooleanAndExpression() (ast.BooleanExpression, error) { } func (p *Parser) parseBooleanPrimaryExpression() (ast.BooleanExpression, error) { + // Check for CONTAINS/FREETEXT predicates + if p.curTok.Type == TokenIdent { + upper := strings.ToUpper(p.curTok.Literal) + if upper == "CONTAINS" || upper == "FREETEXT" { + return p.parseFullTextPredicate(upper) + } + if upper == "EXISTS" { + return p.parseExistsPredicate() + } + } + // Check for parenthesized expression - could be boolean or scalar subquery if p.curTok.Type == TokenLParen { // Peek ahead to see if it's a subquery (SELECT) @@ -4201,6 +4330,43 @@ func (p *Parser) parseBooleanPrimaryExpression() (ast.BooleanExpression, error) }, nil } + // Check for SOME/ANY/ALL (subquery) + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "SOME" || upperLit == "ANY" || upperLit == "ALL" { + predicateType := "Any" + if upperLit == "ALL" { + predicateType = "All" + } + p.nextToken() // consume SOME/ANY/ALL + + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after %s, got %s", upperLit, p.curTok.Literal) + } + p.nextToken() // consume ( + + subqueryExpr, err := p.parseQueryExpression() + if err != nil { + return nil, err + } + + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ), got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + + compType := "IsDistinctFrom" + if isNot { + compType = "IsNotDistinctFrom" + } + + return &ast.SubqueryComparisonPredicate{ + Expression: left, + ComparisonType: compType, + Subquery: &ast.ScalarSubquery{QueryExpression: subqueryExpr}, + SubqueryComparisonPredicateType: predicateType, + }, nil + } + // Parse the second expression secondExpr, err := p.parseScalarExpression() if err != nil { @@ -5245,3 +5411,252 @@ func (p *Parser) parseJsonForClauseOption() (*ast.JsonForClauseOption, error) { return option, nil } + +// parseFullTextPredicate parses CONTAINS or FREETEXT predicates +func (p *Parser) parseFullTextPredicate(funcType string) (*ast.FullTextPredicate, error) { + // Convert to PascalCase: "CONTAINS" -> "Contains", "FREETEXT" -> "FreeText" + pascalType := strings.ToUpper(funcType[:1]) + strings.ToLower(funcType[1:]) + if funcType == "FREETEXT" { + pascalType = "FreeText" + } + pred := &ast.FullTextPredicate{ + FullTextFunctionType: pascalType, + } + p.nextToken() // consume CONTAINS/FREETEXT + + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after %s, got %s", funcType, p.curTok.Literal) + } + p.nextToken() // consume ( + + // Parse column specification: *, column, (columns), or PROPERTY(column, 'prop') + if p.curTok.Type == TokenStar { + pred.Columns = []*ast.ColumnReferenceExpression{{ColumnType: "Wildcard"}} + p.nextToken() // consume * + } else if p.curTok.Type == TokenLParen { + // Column list + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + if p.curTok.Type == TokenStar { + pred.Columns = append(pred.Columns, &ast.ColumnReferenceExpression{ColumnType: "Wildcard"}) + p.nextToken() + } else { + col := p.parseIdentifier() + pred.Columns = append(pred.Columns, &ast.ColumnReferenceExpression{ + ColumnType: "Regular", + MultiPartIdentifier: &ast.MultiPartIdentifier{ + Identifiers: []*ast.Identifier{col}, + Count: 1, + }, + }) + } + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } else if p.curTok.Type == TokenIdent && strings.ToUpper(p.curTok.Literal) == "PROPERTY" { + // PROPERTY(column, 'property_name') + p.nextToken() // consume PROPERTY + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after PROPERTY, got %s", p.curTok.Literal) + } + p.nextToken() // consume ( + + // Parse column name + col := p.parseIdentifier() + pred.Columns = []*ast.ColumnReferenceExpression{{ + ColumnType: "Regular", + MultiPartIdentifier: &ast.MultiPartIdentifier{ + Identifiers: []*ast.Identifier{col}, + Count: 1, + }, + }} + + // Expect comma + if p.curTok.Type != TokenComma { + return nil, fmt.Errorf("expected , after column in PROPERTY, got %s", p.curTok.Literal) + } + p.nextToken() // consume , + + // Parse property name (string literal) + propExpr, err := p.parsePrimaryExpression() + if err != nil { + return nil, err + } + pred.PropertyName = propExpr + + // Expect ) + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) after PROPERTY, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + } else { + // Single column + col := p.parseIdentifier() + pred.Columns = []*ast.ColumnReferenceExpression{{ + ColumnType: "Regular", + MultiPartIdentifier: &ast.MultiPartIdentifier{ + Identifiers: []*ast.Identifier{col}, + Count: 1, + }, + }} + } + + // Expect comma + if p.curTok.Type != TokenComma { + return nil, fmt.Errorf("expected , after columns in %s, got %s", funcType, p.curTok.Literal) + } + p.nextToken() // consume , + + // Parse search value + value, err := p.parsePrimaryExpression() + if err != nil { + return nil, err + } + pred.Value = value + + // Parse optional LANGUAGE term + if p.curTok.Type == TokenComma { + p.nextToken() // consume , + if p.curTok.Type == TokenLanguage { + p.nextToken() // consume LANGUAGE + langTerm, err := p.parsePrimaryExpression() + if err != nil { + return nil, err + } + pred.LanguageTerm = langTerm + } + } + + // Expect ) + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) after %s, got %s", funcType, p.curTok.Literal) + } + p.nextToken() // consume ) + + return pred, nil +} + +// parseExistsPredicate parses EXISTS (subquery) +func (p *Parser) parseExistsPredicate() (*ast.ExistsPredicate, error) { + p.nextToken() // consume EXISTS + + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after EXISTS, got %s", p.curTok.Literal) + } + p.nextToken() // consume ( + + // Parse subquery + subquery, err := p.parseQueryExpression() + if err != nil { + return nil, err + } + + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) after EXISTS subquery, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + + return &ast.ExistsPredicate{Subquery: subquery}, nil +} + +// parseIIfCall parses IIF(condition, true_value, false_value) +func (p *Parser) parseIIfCall() (*ast.IIfCall, error) { + p.nextToken() // consume ( + + // Parse boolean predicate + pred, err := p.parseBooleanExpression() + if err != nil { + return nil, err + } + + if p.curTok.Type != TokenComma { + return nil, fmt.Errorf("expected , after IIF condition, got %s", p.curTok.Literal) + } + p.nextToken() // consume , + + // Parse then expression + thenExpr, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + + if p.curTok.Type != TokenComma { + return nil, fmt.Errorf("expected , after IIF then expression, got %s", p.curTok.Literal) + } + p.nextToken() // consume , + + // Parse else expression + elseExpr, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) after IIF, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + + return &ast.IIfCall{ + Predicate: pred, + ThenExpression: thenExpr, + ElseExpression: elseExpr, + }, nil +} + +// parseParseCall parses PARSE(string AS type [USING culture]) or TRY_PARSE(string AS type [USING culture]) +func (p *Parser) parseParseCall(isTry bool) (ast.ScalarExpression, error) { + p.nextToken() // consume ( + + // Parse string value expression + strVal, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + + // Expect AS + if strings.ToUpper(p.curTok.Literal) != "AS" { + return nil, fmt.Errorf("expected AS after PARSE value, got %s", p.curTok.Literal) + } + p.nextToken() // consume AS + + // Parse data type + dataType, err := p.parseDataType() + if err != nil { + return nil, err + } + + var culture ast.ScalarExpression + + // Check for USING culture + if strings.ToUpper(p.curTok.Literal) == "USING" { + p.nextToken() // consume USING + culture, err = p.parseScalarExpression() + if err != nil { + return nil, err + } + } + + if p.curTok.Type != TokenRParen { + return nil, fmt.Errorf("expected ) after PARSE, got %s", p.curTok.Literal) + } + p.nextToken() // consume ) + + if isTry { + return &ast.TryParseCall{ + StringValue: strVal, + DataType: dataType, + Culture: culture, + }, nil + } + return &ast.ParseCall{ + StringValue: strVal, + DataType: dataType, + Culture: culture, + }, nil +} diff --git a/parser/parse_statements.go b/parser/parse_statements.go index e6f6bc31..af18b5cc 100644 --- a/parser/parse_statements.go +++ b/parser/parse_statements.go @@ -452,23 +452,33 @@ func (p *Parser) parseInlineIndexDefinition() (*ast.IndexDefinition, error) { p.nextToken() } - // Parse optional CLUSTERED/NONCLUSTERED [HASH] + // Parse optional CLUSTERED/NONCLUSTERED [HASH/COLUMNSTORE] if strings.ToUpper(p.curTok.Literal) == "CLUSTERED" { indexDef.IndexType = &ast.IndexType{IndexTypeKind: "Clustered"} p.nextToken() - // Check for HASH + // Check for HASH or COLUMNSTORE if strings.ToUpper(p.curTok.Literal) == "HASH" { indexDef.IndexType.IndexTypeKind = "ClusteredHash" p.nextToken() + } else if strings.ToUpper(p.curTok.Literal) == "COLUMNSTORE" { + indexDef.IndexType.IndexTypeKind = "ClusteredColumnStore" + p.nextToken() } } else if strings.ToUpper(p.curTok.Literal) == "NONCLUSTERED" { indexDef.IndexType = &ast.IndexType{IndexTypeKind: "NonClustered"} p.nextToken() - // Check for HASH + // Check for HASH or COLUMNSTORE if strings.ToUpper(p.curTok.Literal) == "HASH" { indexDef.IndexType.IndexTypeKind = "NonClusteredHash" p.nextToken() + } else if strings.ToUpper(p.curTok.Literal) == "COLUMNSTORE" { + indexDef.IndexType.IndexTypeKind = "NonClusteredColumnStore" + p.nextToken() } + } else if strings.ToUpper(p.curTok.Literal) == "COLUMNSTORE" { + // Implicit NONCLUSTERED COLUMNSTORE + indexDef.IndexType = &ast.IndexType{IndexTypeKind: "NonClusteredColumnStore"} + p.nextToken() } // Parse column list @@ -565,6 +575,16 @@ func (p *Parser) parseInlineIndexDefinition() (*ast.IndexDefinition, error) { } } + // Parse optional WHERE clause for filtered indexes + if strings.ToUpper(p.curTok.Literal) == "WHERE" { + p.nextToken() // consume WHERE + filterPredicate, err := p.parseBooleanExpression() + if err != nil { + return nil, err + } + indexDef.FilterPredicate = filterPredicate + } + // Parse optional WITH options if strings.ToUpper(p.curTok.Literal) == "WITH" { p.nextToken() // consume WITH @@ -577,7 +597,8 @@ func (p *Parser) parseInlineIndexDefinition() (*ast.IndexDefinition, error) { p.nextToken() // consume = } // Parse option value - if optionName == "BUCKET_COUNT" { + switch optionName { + case "BUCKET_COUNT": opt := &ast.IndexExpressionOption{ OptionKind: "BucketCount", Expression: &ast.IntegerLiteral{ @@ -587,8 +608,128 @@ func (p *Parser) parseInlineIndexDefinition() (*ast.IndexDefinition, error) { } indexDef.IndexOptions = append(indexDef.IndexOptions, opt) p.nextToken() - } else { - // Skip other options + case "FILLFACTOR": + opt := &ast.IndexExpressionOption{ + OptionKind: "FillFactor", + Expression: &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + }, + } + indexDef.IndexOptions = append(indexDef.IndexOptions, opt) + p.nextToken() + case "PAD_INDEX", "STATISTICS_NORECOMPUTE", "ALLOW_ROW_LOCKS", "ALLOW_PAGE_LOCKS": + optionKindMap := map[string]string{ + "PAD_INDEX": "PadIndex", + "STATISTICS_NORECOMPUTE": "StatisticsNoRecompute", + "ALLOW_ROW_LOCKS": "AllowRowLocks", + "ALLOW_PAGE_LOCKS": "AllowPageLocks", + } + state := strings.ToUpper(p.curTok.Literal) + optState := "Off" + if state == "ON" { + optState = "On" + } + opt := &ast.IndexStateOption{ + OptionKind: optionKindMap[optionName], + OptionState: optState, + } + indexDef.IndexOptions = append(indexDef.IndexOptions, opt) + p.nextToken() + case "IGNORE_DUP_KEY": + state := strings.ToUpper(p.curTok.Literal) + optState := "Off" + if state == "ON" { + optState = "On" + } + opt := &ast.IgnoreDupKeyIndexOption{ + OptionKind: "IgnoreDupKey", + OptionState: optState, + } + indexDef.IndexOptions = append(indexDef.IndexOptions, opt) + p.nextToken() + case "DATA_COMPRESSION": + compressionLevel := "None" + levelStr := strings.ToUpper(p.curTok.Literal) + switch levelStr { + case "NONE": + compressionLevel = "None" + case "ROW": + compressionLevel = "Row" + case "PAGE": + compressionLevel = "Page" + case "COLUMNSTORE": + compressionLevel = "ColumnStore" + case "COLUMNSTORE_ARCHIVE": + compressionLevel = "ColumnStoreArchive" + } + p.nextToken() // consume the compression level + opt := &ast.DataCompressionOption{ + OptionKind: "DataCompression", + CompressionLevel: compressionLevel, + } + // Check for optional ON PARTITIONS(range) + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + if strings.ToUpper(p.curTok.Literal) == "PARTITIONS" { + p.nextToken() // consume PARTITIONS + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + pr := &ast.CompressionPartitionRange{} + // Parse From value + from := &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + } + pr.From = from + p.nextToken() + // Check for TO + if strings.ToUpper(p.curTok.Literal) == "TO" { + p.nextToken() // consume TO + to := &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + } + pr.To = to + p.nextToken() + } + opt.PartitionRanges = append(opt.PartitionRanges, pr) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + } + } + indexDef.IndexOptions = append(indexDef.IndexOptions, opt) + case "COMPRESSION_DELAY": + opt := &ast.CompressionDelayIndexOption{ + OptionKind: "CompressionDelay", + Expression: &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + }, + TimeUnit: "Unitless", + } + p.nextToken() // consume the number + // Check for optional MINUTE/MINUTES time unit + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "MINUTE" { + opt.TimeUnit = "Minute" + p.nextToken() + } else if upperLit == "MINUTES" { + opt.TimeUnit = "Minutes" + p.nextToken() + } + indexDef.IndexOptions = append(indexDef.IndexOptions, opt) + default: + // Skip unknown options p.nextToken() } if p.curTok.Type == TokenComma { @@ -603,6 +744,27 @@ func (p *Parser) parseInlineIndexDefinition() (*ast.IndexDefinition, error) { } } + // Parse optional ON filegroup clause + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + fg := &ast.FileGroupOrPartitionScheme{ + Name: &ast.IdentifierOrValueExpression{ + Value: p.curTok.Literal, + Identifier: p.parseIdentifier(), + }, + } + indexDef.OnFileGroupOrPartitionScheme = fg + } + + // Parse optional FILESTREAM_ON clause + if strings.ToUpper(p.curTok.Literal) == "FILESTREAM_ON" { + p.nextToken() // consume FILESTREAM_ON + indexDef.FileStreamOn = &ast.IdentifierOrValueExpression{ + Value: p.curTok.Literal, + Identifier: p.parseIdentifier(), + } + } + return indexDef, nil } @@ -1892,10 +2054,22 @@ func (p *Parser) parseBeginAtomicBlockStatement() (*ast.BeginEndAtomicBlockState p.nextToken() // consume = } } - // Parse the isolation level identifier + // Parse the isolation level identifier - may be multi-word like "READ COMMITTED" + levelValue := strings.ToUpper(p.curTok.Literal) + p.nextToken() + // Check for two-word isolation levels + nextWord := strings.ToUpper(p.curTok.Literal) + if (levelValue == "READ" && (nextWord == "COMMITTED" || nextWord == "UNCOMMITTED")) || + (levelValue == "REPEATABLE" && nextWord == "READ") { + levelValue = levelValue + " " + nextWord + p.nextToken() + } opt := &ast.IdentifierAtomicBlockOption{ OptionKind: "IsolationLevel", - Value: p.parseIdentifier(), + Value: &ast.Identifier{ + Value: levelValue, + QuoteType: "NotQuoted", + }, } stmt.Options = append(stmt.Options, opt) case "LANGUAGE": @@ -1929,10 +2103,48 @@ func (p *Parser) parseBeginAtomicBlockStatement() (*ast.BeginEndAtomicBlockState } stmt.Options = append(stmt.Options, opt) } - case "DATEFIRST", "DATEFORMAT": - opt := &ast.IdentifierAtomicBlockOption{ - OptionKind: optName, - Value: p.parseIdentifier(), + case "DATEFIRST": + // Parse as integer literal + intLit := &ast.IntegerLiteral{ + LiteralType: "Integer", + Value: p.curTok.Literal, + } + p.nextToken() + opt := &ast.LiteralAtomicBlockOption{ + OptionKind: "DateFirst", + Value: intLit, + } + stmt.Options = append(stmt.Options, opt) + case "DATEFORMAT": + // Parse as string literal + value := p.curTok.Literal + // Strip quotes if present + if len(value) >= 2 && value[0] == '\'' && value[len(value)-1] == '\'' { + value = value[1 : len(value)-1] + } + strLit := &ast.StringLiteral{ + LiteralType: "String", + Value: value, + IsNational: false, + IsLargeObject: false, + } + p.nextToken() + opt := &ast.LiteralAtomicBlockOption{ + OptionKind: "DateFormat", + Value: strLit, + } + stmt.Options = append(stmt.Options, opt) + case "DELAYED_DURABILITY": + // Parse ON/OFF as OnOffAtomicBlockOption + stateUpper := strings.ToUpper(p.curTok.Literal) + optState := "Off" + if stateUpper == "ON" { + optState = "On" + } + p.nextToken() + opt := &ast.OnOffAtomicBlockOption{ + OptionKind: "DelayedDurability", + OptionState: optState, } stmt.Options = append(stmt.Options, opt) default: @@ -2626,6 +2838,8 @@ func (p *Parser) parseCreateStatement() (ast.Statement, error) { default: return nil, fmt.Errorf("expected ROLE or AUDIT after SERVER, got %s", p.curTok.Literal) } + case "AVAILABILITY": + return p.parseCreateAvailabilityGroupStatement() } // Lenient: skip unknown CREATE statements p.skipToEndOfStatement() @@ -2644,6 +2858,278 @@ func (p *Parser) parseCreateStatement() (ast.Statement, error) { } } +func (p *Parser) parseCreateAvailabilityGroupStatement() (*ast.CreateAvailabilityGroupStatement, error) { + // Consume AVAILABILITY + p.nextToken() + + // Expect GROUP + if strings.ToUpper(p.curTok.Literal) != "GROUP" { + return nil, fmt.Errorf("expected GROUP after AVAILABILITY, got %s", p.curTok.Literal) + } + p.nextToken() + + stmt := &ast.CreateAvailabilityGroupStatement{} + + // Parse group name + stmt.Name = p.parseIdentifier() + + // Parse WITH clause for group options + if p.curTok.Type == TokenWith || strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optName := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume option name + + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + + switch optName { + case "REQUIRED_COPIES_TO_COMMIT": + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, &ast.LiteralAvailabilityGroupOption{ + OptionKind: "RequiredCopiesToCommit", + Value: val, + }) + default: + // Skip unknown options + if p.curTok.Type != TokenComma && p.curTok.Type != TokenRParen { + p.nextToken() + } + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + } + + // Parse FOR DATABASE clause + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "DATABASE" { + p.nextToken() // consume DATABASE + // Parse comma-separated database names + for { + stmt.Databases = append(stmt.Databases, p.parseIdentifier()) + if p.curTok.Type != TokenComma { + break + } + p.nextToken() // consume comma + } + } + } + + // Parse REPLICA ON clause + if strings.ToUpper(p.curTok.Literal) == "REPLICA" { + p.nextToken() // consume REPLICA + if strings.ToUpper(p.curTok.Literal) == "ON" { + p.nextToken() // consume ON + } + + // Parse comma-separated replica definitions + for { + replica := &ast.AvailabilityReplica{} + + // Parse server name (string literal) + if p.curTok.Type == TokenString { + replica.ServerName, _ = p.parseStringLiteral() + } + + // Parse WITH clause for replica options + if p.curTok.Type == TokenWith || strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optName := strings.ToUpper(p.curTok.Literal) + p.nextToken() // consume option name + + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + + switch optName { + case "AVAILABILITY_MODE": + modeStr := strings.ToUpper(p.curTok.Literal) + p.nextToken() + // Handle SYNCHRONOUS_COMMIT or ASYNCHRONOUS_COMMIT + if p.curTok.Type == TokenIdent && strings.HasPrefix(strings.ToUpper(p.curTok.Literal), "_") { + modeStr += strings.ToUpper(p.curTok.Literal) + p.nextToken() + } + var mode string + switch modeStr { + case "SYNCHRONOUS_COMMIT": + mode = "SynchronousCommit" + case "ASYNCHRONOUS_COMMIT": + mode = "AsynchronousCommit" + default: + mode = modeStr + } + replica.Options = append(replica.Options, &ast.AvailabilityModeReplicaOption{ + OptionKind: "AvailabilityMode", + Value: mode, + }) + case "FAILOVER_MODE": + modeStr := strings.ToUpper(p.curTok.Literal) + p.nextToken() + var mode string + switch modeStr { + case "AUTOMATIC": + mode = "Automatic" + case "MANUAL": + mode = "Manual" + default: + mode = modeStr + } + replica.Options = append(replica.Options, &ast.FailoverModeReplicaOption{ + OptionKind: "FailoverMode", + Value: mode, + }) + case "ENDPOINT_URL": + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + replica.Options = append(replica.Options, &ast.LiteralReplicaOption{ + OptionKind: "EndpointUrl", + Value: val, + }) + case "SESSION_TIMEOUT": + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + replica.Options = append(replica.Options, &ast.LiteralReplicaOption{ + OptionKind: "SessionTimeout", + Value: val, + }) + case "APPLY_DELAY": + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + replica.Options = append(replica.Options, &ast.LiteralReplicaOption{ + OptionKind: "ApplyDelay", + Value: val, + }) + case "PRIMARY_ROLE": + // Parse (ALLOW_CONNECTIONS = ...) + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + innerOpt := strings.ToUpper(p.curTok.Literal) + p.nextToken() + if p.curTok.Type == TokenEquals { + p.nextToken() + } + if innerOpt == "ALLOW_CONNECTIONS" { + connMode := strings.ToUpper(p.curTok.Literal) + p.nextToken() + var mode string + switch connMode { + case "READ_WRITE": + mode = "ReadWrite" + case "ALL": + mode = "All" + default: + mode = connMode + } + replica.Options = append(replica.Options, &ast.PrimaryRoleReplicaOption{ + OptionKind: "PrimaryRole", + AllowConnections: mode, + }) + } + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + case "SECONDARY_ROLE": + // Parse (ALLOW_CONNECTIONS = ...) + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + innerOpt := strings.ToUpper(p.curTok.Literal) + p.nextToken() + if p.curTok.Type == TokenEquals { + p.nextToken() + } + if innerOpt == "ALLOW_CONNECTIONS" { + connMode := strings.ToUpper(p.curTok.Literal) + p.nextToken() + var mode string + switch connMode { + case "NO": + mode = "No" + case "READ_ONLY": + mode = "ReadOnly" + case "ALL": + mode = "All" + default: + mode = connMode + } + replica.Options = append(replica.Options, &ast.SecondaryRoleReplicaOption{ + OptionKind: "SecondaryRole", + AllowConnections: mode, + }) + } + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + default: + // Skip unknown options + if p.curTok.Type != TokenComma && p.curTok.Type != TokenRParen { + p.nextToken() + } + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() + } + } + } + + stmt.Replicas = append(stmt.Replicas, replica) + + if p.curTok.Type == TokenComma { + p.nextToken() // consume comma + } else { + break + } + } + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + func (p *Parser) parseCreateCryptographicProviderStatement() (*ast.CreateCryptographicProviderStatement, error) { // Consume CRYPTOGRAPHIC p.nextToken() @@ -2912,10 +3398,15 @@ func (p *Parser) parseCreateServerRoleStatementBody() (*ast.CreateServerRoleStat return stmt, nil } -func (p *Parser) parseCreateServerAuditStatement() (*ast.CreateServerAuditStatement, error) { +func (p *Parser) parseCreateServerAuditStatement() (ast.Statement, error) { // AUDIT keyword should be current token, consume it p.nextToken() + // Check if this is CREATE SERVER AUDIT SPECIFICATION + if strings.ToUpper(p.curTok.Literal) == "SPECIFICATION" { + return p.parseCreateServerAuditSpecificationStatement() + } + stmt := &ast.CreateServerAuditStatement{} // Parse audit name @@ -2954,28 +3445,350 @@ func (p *Parser) parseCreateServerAuditStatement() (*ast.CreateServerAuditStatem } } - // Parse WHERE clause (predicate) - if strings.ToUpper(p.curTok.Literal) == "WHERE" { - p.nextToken() // consume WHERE - pred, err := p.parseAuditPredicate() - if err != nil { - return nil, err - } - stmt.PredicateExpression = pred - } + // Parse WHERE clause (predicate) + if strings.ToUpper(p.curTok.Literal) == "WHERE" { + p.nextToken() // consume WHERE + pred, err := p.parseAuditPredicate() + if err != nil { + return nil, err + } + stmt.PredicateExpression = pred + } + + // Skip optional semicolon + if p.curTok.Type == TokenSemicolon { + p.nextToken() + } + + return stmt, nil +} + +func (p *Parser) parseCreateServerAuditSpecificationStatement() (*ast.CreateServerAuditSpecificationStatement, error) { + // SPECIFICATION keyword should be current token, consume it + p.nextToken() + + stmt := &ast.CreateServerAuditSpecificationStatement{ + AuditState: "NotSet", + } + + // Parse specification name + stmt.SpecificationName = p.parseIdentifier() + + // Parse FOR SERVER AUDIT audit_name + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "SERVER" { + p.nextToken() // consume SERVER + } + if strings.ToUpper(p.curTok.Literal) == "AUDIT" { + p.nextToken() // consume AUDIT + } + stmt.AuditName = p.parseIdentifier() + } + + // Parse ADD/DROP parts + for { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "ADD" || upperLit == "DROP" { + part := &ast.AuditSpecificationPart{ + IsDrop: upperLit == "DROP", + } + p.nextToken() // consume ADD/DROP + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + // Parse audit action group reference + groupName := p.curTok.Literal + part.Details = &ast.AuditActionGroupReference{ + Group: convertAuditGroupName(groupName), + } + p.nextToken() // consume group name + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + stmt.Parts = append(stmt.Parts, part) + if p.curTok.Type == TokenComma { + p.nextToken() // consume , + continue + } + } + break + } + + // Parse WITH (STATE = ON/OFF) + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + if strings.ToUpper(p.curTok.Literal) == "STATE" { + p.nextToken() // consume STATE + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + if strings.ToUpper(p.curTok.Literal) == "ON" { + stmt.AuditState = "On" + } else if strings.ToUpper(p.curTok.Literal) == "OFF" { + stmt.AuditState = "Off" + } + p.nextToken() // consume ON/OFF + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } + + return stmt, nil +} + +func (p *Parser) parseAlterServerAuditSpecificationStatement() (*ast.AlterServerAuditSpecificationStatement, error) { + // SPECIFICATION keyword should be current token, consume it + p.nextToken() + + stmt := &ast.AlterServerAuditSpecificationStatement{ + AuditState: "NotSet", + } + + // Parse specification name + stmt.SpecificationName = p.parseIdentifier() + + // Parse FOR SERVER AUDIT audit_name + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "SERVER" { + p.nextToken() // consume SERVER + } + if strings.ToUpper(p.curTok.Literal) == "AUDIT" { + p.nextToken() // consume AUDIT + } + stmt.AuditName = p.parseIdentifier() + } + + // Parse ADD/DROP parts + for { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "ADD" || upperLit == "DROP" { + part := &ast.AuditSpecificationPart{ + IsDrop: upperLit == "DROP", + } + p.nextToken() // consume ADD/DROP + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + // Parse audit action group reference + groupName := p.curTok.Literal + part.Details = &ast.AuditActionGroupReference{ + Group: convertAuditGroupName(groupName), + } + p.nextToken() // consume group name + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + stmt.Parts = append(stmt.Parts, part) + if p.curTok.Type == TokenComma { + p.nextToken() // consume , + continue + } + } + break + } + + // Parse WITH (STATE = ON/OFF) + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + if strings.ToUpper(p.curTok.Literal) == "STATE" { + p.nextToken() // consume STATE + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + if strings.ToUpper(p.curTok.Literal) == "ON" { + stmt.AuditState = "On" + } else if strings.ToUpper(p.curTok.Literal) == "OFF" { + stmt.AuditState = "Off" + } + p.nextToken() // consume ON/OFF + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } + + return stmt, nil +} + +func (p *Parser) parseCreateDatabaseAuditSpecificationStatement() (*ast.CreateDatabaseAuditSpecificationStatement, error) { + stmt := &ast.CreateDatabaseAuditSpecificationStatement{ + AuditState: "NotSet", + } + + // Parse specification name + stmt.SpecificationName = p.parseIdentifier() + + // Parse FOR SERVER AUDIT audit_name + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "SERVER" { + p.nextToken() // consume SERVER + } + if strings.ToUpper(p.curTok.Literal) == "AUDIT" { + p.nextToken() // consume AUDIT + } + stmt.AuditName = p.parseIdentifier() + } + + // Parse ADD/DROP parts + for { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "ADD" || upperLit == "DROP" { + part := &ast.AuditSpecificationPart{ + IsDrop: upperLit == "DROP", + } + p.nextToken() // consume ADD/DROP + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + // Parse audit action group reference + groupName := p.curTok.Literal + part.Details = &ast.AuditActionGroupReference{ + Group: convertAuditGroupName(groupName), + } + p.nextToken() // consume group name + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + stmt.Parts = append(stmt.Parts, part) + if p.curTok.Type == TokenComma { + p.nextToken() // consume , + continue + } + } + break + } + + // Parse WITH (STATE = ON/OFF) + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + if strings.ToUpper(p.curTok.Literal) == "STATE" { + p.nextToken() // consume STATE + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + if strings.ToUpper(p.curTok.Literal) == "ON" { + stmt.AuditState = "On" + } else if strings.ToUpper(p.curTok.Literal) == "OFF" { + stmt.AuditState = "Off" + } + p.nextToken() // consume ON/OFF + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } + + return stmt, nil +} + +func (p *Parser) parseAlterDatabaseAuditSpecificationStatement() (*ast.AlterDatabaseAuditSpecificationStatement, error) { + stmt := &ast.AlterDatabaseAuditSpecificationStatement{ + AuditState: "NotSet", + } + + // Parse specification name + stmt.SpecificationName = p.parseIdentifier() + + // Parse FOR SERVER AUDIT audit_name (optional in ALTER) + if strings.ToUpper(p.curTok.Literal) == "FOR" { + p.nextToken() // consume FOR + if strings.ToUpper(p.curTok.Literal) == "SERVER" { + p.nextToken() // consume SERVER + } + if strings.ToUpper(p.curTok.Literal) == "AUDIT" { + p.nextToken() // consume AUDIT + } + stmt.AuditName = p.parseIdentifier() + } + + // Parse ADD/DROP parts + for { + upperLit := strings.ToUpper(p.curTok.Literal) + if upperLit == "ADD" || upperLit == "DROP" { + part := &ast.AuditSpecificationPart{ + IsDrop: upperLit == "DROP", + } + p.nextToken() // consume ADD/DROP + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + // Parse audit action group reference + groupName := p.curTok.Literal + part.Details = &ast.AuditActionGroupReference{ + Group: convertAuditGroupName(groupName), + } + p.nextToken() // consume group name + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + stmt.Parts = append(stmt.Parts, part) + if p.curTok.Type == TokenComma { + p.nextToken() // consume , + continue + } + } + break + } + + // Parse WITH (STATE = ON/OFF) + if strings.ToUpper(p.curTok.Literal) == "WITH" { + p.nextToken() // consume WITH + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + if strings.ToUpper(p.curTok.Literal) == "STATE" { + p.nextToken() // consume STATE + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + if strings.ToUpper(p.curTok.Literal) == "ON" { + stmt.AuditState = "On" + } else if strings.ToUpper(p.curTok.Literal) == "OFF" { + stmt.AuditState = "Off" + } + p.nextToken() // consume ON/OFF + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } + + return stmt, nil +} - // Skip optional semicolon - if p.curTok.Type == TokenSemicolon { - p.nextToken() +// convertAuditGroupName converts an audit group name to the expected format +func convertAuditGroupName(name string) string { + // Map of audit group names to their expected format + groupMap := map[string]string{ + "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP": "SuccessfulDatabaseAuthenticationGroup", + "FAILED_DATABASE_AUTHENTICATION_GROUP": "FailedDatabaseAuthenticationGroup", + "DATABASE_LOGOUT_GROUP": "DatabaseLogoutGroup", + "USER_CHANGE_PASSWORD_GROUP": "UserChangePasswordGroup", + "USER_DEFINED_AUDIT_GROUP": "UserDefinedAuditGroup", } - - return stmt, nil + if mapped, ok := groupMap[strings.ToUpper(name)]; ok { + return mapped + } + return capitalizeFirst(strings.ToLower(strings.ReplaceAll(name, "_", " "))) } func (p *Parser) parseAuditTarget() (*ast.AuditTarget, error) { target := &ast.AuditTarget{} - // Parse target kind (FILE, APPLICATION_LOG, SECURITY_LOG) + // Parse target kind (FILE, APPLICATION_LOG, SECURITY_LOG, URL, EXTERNAL_MONITOR) switch strings.ToUpper(p.curTok.Literal) { case "FILE": target.TargetKind = "File" @@ -2983,6 +3796,10 @@ func (p *Parser) parseAuditTarget() (*ast.AuditTarget, error) { target.TargetKind = "ApplicationLog" case "SECURITY_LOG": target.TargetKind = "SecurityLog" + case "URL": + target.TargetKind = "Url" + case "EXTERNAL_MONITOR": + target.TargetKind = "ExternalMonitor" default: target.TargetKind = capitalizeFirst(p.curTok.Literal) } @@ -3084,6 +3901,17 @@ func (p *Parser) parseAuditTargetOption() (ast.AuditTargetOption, error) { Value: value, }, nil + case "RETENTION_DAYS": + // Parse the number of days + days, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + return &ast.RetentionDaysAuditTargetOption{ + OptionKind: "RetentionDays", + Days: days, + }, nil + default: // Parse literal value (FILEPATH, etc.) val, err := p.parseScalarExpression() @@ -3533,6 +4361,12 @@ func (p *Parser) parseCreateProcedureStatement() (*ast.CreateProcedureStatement, } else if upperLit == "ENCRYPTION" { stmt.Options = append(stmt.Options, &ast.ProcedureOption{OptionKind: "Encryption"}) p.nextToken() + } else if upperLit == "NATIVE_COMPILATION" { + stmt.Options = append(stmt.Options, &ast.ProcedureOption{OptionKind: "NativeCompilation"}) + p.nextToken() + } else if upperLit == "SCHEMABINDING" { + stmt.Options = append(stmt.Options, &ast.ProcedureOption{OptionKind: "SchemaBinding"}) + p.nextToken() } else if upperLit == "EXECUTE" { p.nextToken() // consume EXECUTE if p.curTok.Type == TokenAs { @@ -3793,9 +4627,21 @@ func (p *Parser) parseCreateViewStatement() (*ast.CreateViewStatement, error) { // Check for WITH options if p.curTok.Type == TokenWith { p.nextToken() - // Parse view options - for p.curTok.Type == TokenIdent { - opt := &ast.ViewStatementOption{OptionKind: p.curTok.Literal} + // Parse view options (can be identifiers or keywords like ENCRYPTION) + for p.curTok.Type != TokenAs && p.curTok.Type != TokenEOF && p.curTok.Type != TokenSemicolon { + optName := strings.ToUpper(p.curTok.Literal) + var optionKind string + switch optName { + case "ENCRYPTION": + optionKind = "Encryption" + case "SCHEMABINDING": + optionKind = "SchemaBinding" + case "VIEW_METADATA": + optionKind = "ViewMetadata" + default: + optionKind = p.curTok.Literal + } + opt := &ast.ViewStatementOption{OptionKind: optionKind} stmt.ViewOptions = append(stmt.ViewOptions, opt) p.nextToken() if p.curTok.Type == TokenComma { @@ -3820,6 +4666,18 @@ func (p *Parser) parseCreateViewStatement() (*ast.CreateViewStatement, error) { } stmt.SelectStatement = selStmt + // Check for WITH CHECK OPTION + if p.curTok.Type == TokenWith { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "CHECK" { + p.nextToken() + if strings.ToUpper(p.curTok.Literal) == "OPTION" { + p.nextToken() + stmt.WithCheckOption = true + } + } + } + return stmt, nil } @@ -6396,51 +7254,63 @@ func (p *Parser) parseBackupStatement() (ast.Statement, error) { } // Parse optional WITH clause - var options []*ast.BackupOption + var options []ast.BackupOptionBase if p.curTok.Type == TokenWith { p.nextToken() for { optionName := strings.ToUpper(p.curTok.Literal) - option := &ast.BackupOption{} - switch optionName { - case "COMPRESSION": - option.OptionKind = "Compression" - case "NO_COMPRESSION": - option.OptionKind = "NoCompression" - case "STOP_ON_ERROR": - option.OptionKind = "StopOnError" - case "CONTINUE_AFTER_ERROR": - option.OptionKind = "ContinueAfterError" - case "CHECKSUM": - option.OptionKind = "Checksum" - case "NO_CHECKSUM": - option.OptionKind = "NoChecksum" - case "INIT": - option.OptionKind = "Init" - case "NOINIT": - option.OptionKind = "NoInit" - case "FORMAT": - option.OptionKind = "Format" - case "NOFORMAT": - option.OptionKind = "NoFormat" - default: - option.OptionKind = optionName - } - p.nextToken() - - // Check for = value - if p.curTok.Type == TokenEquals { - p.nextToken() - val, err := p.parseScalarExpression() + // Check for ENCRYPTION with parentheses + if optionName == "ENCRYPTION" && p.peekTok.Type == TokenLParen { + encOpt, err := p.parseBackupEncryptionOption() if err != nil { return nil, err } - option.Value = val - } + options = append(options, encOpt) + } else { + option := &ast.BackupOption{} + + switch optionName { + case "COMPRESSION": + option.OptionKind = "Compression" + case "NO_COMPRESSION": + option.OptionKind = "NoCompression" + case "STOP_ON_ERROR": + option.OptionKind = "StopOnError" + case "CONTINUE_AFTER_ERROR": + option.OptionKind = "ContinueAfterError" + case "CHECKSUM": + option.OptionKind = "Checksum" + case "NO_CHECKSUM": + option.OptionKind = "NoChecksum" + case "INIT": + option.OptionKind = "Init" + case "NOINIT": + option.OptionKind = "NoInit" + case "FORMAT": + option.OptionKind = "Format" + case "NOFORMAT": + option.OptionKind = "NoFormat" + case "STATS": + option.OptionKind = "Stats" + default: + option.OptionKind = optionName + } + p.nextToken() + + // Check for = value + if p.curTok.Type == TokenEquals { + p.nextToken() + val, err := p.parseScalarExpression() + if err != nil { + return nil, err + } + option.Value = val + } - options = append(options, option) + options = append(options, option) + } if p.curTok.Type == TokenComma { p.nextToken() @@ -6471,6 +7341,85 @@ func (p *Parser) parseBackupStatement() (ast.Statement, error) { }, nil } +func (p *Parser) parseBackupEncryptionOption() (*ast.BackupEncryptionOption, error) { + // curTok is ENCRYPTION + p.nextToken() // consume ENCRYPTION + + if p.curTok.Type != TokenLParen { + return nil, fmt.Errorf("expected ( after ENCRYPTION, got %s", p.curTok.Literal) + } + p.nextToken() // consume ( + + opt := &ast.BackupEncryptionOption{ + OptionKind: "None", + } + + // Parse options inside parentheses + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + optName := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + + switch optName { + case "ALGORITHM": + // Parse algorithm type: AES_128, AES_192, AES_256, TRIPLE_DES_3KEY + algName := strings.ToUpper(p.curTok.Literal) + switch algName { + case "AES_128": + opt.Algorithm = "Aes128" + case "AES_192": + opt.Algorithm = "Aes192" + case "AES_256": + opt.Algorithm = "Aes256" + case "TRIPLE_DES_3KEY": + opt.Algorithm = "TripleDes3Key" + default: + opt.Algorithm = algName + } + p.nextToken() + case "SERVER": + // SERVER CERTIFICATE or SERVER ASYMMETRIC KEY + mechType := strings.ToUpper(p.curTok.Literal) + p.nextToken() + + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + + opt.Encryptor = &ast.CryptoMechanism{} + switch mechType { + case "CERTIFICATE": + opt.Encryptor.CryptoMechanismType = "Certificate" + case "ASYMMETRIC": + // Consume KEY + if p.curTok.Type == TokenKey { + p.nextToken() + } + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + opt.Encryptor.CryptoMechanismType = "AsymmetricKey" + } + + // Parse identifier + opt.Encryptor.Identifier = p.parseIdentifier() + } + + if p.curTok.Type == TokenComma { + p.nextToken() + } + } + + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + + return opt, nil +} + func (p *Parser) parseBackupCertificateStatement() (*ast.BackupCertificateStatement, error) { // Consume CERTIFICATE p.nextToken() @@ -7224,6 +8173,45 @@ func (p *Parser) parseCreateExternalTableStatement() (*ast.CreateExternalTableSt switch optName { case "DATA_SOURCE": stmt.DataSource = p.parseIdentifier() + case "REJECT_TYPE": + opt := &ast.ExternalTableRejectTypeOption{ + OptionKind: "RejectType", + } + // VALUE or PERCENTAGE + val := strings.ToUpper(p.curTok.Literal) + switch val { + case "VALUE": + opt.Value = "Value" + case "PERCENTAGE": + opt.Value = "Percentage" + default: + opt.Value = val + } + p.nextToken() // consume value + stmt.ExternalTableOptions = append(stmt.ExternalTableOptions, opt) + case "REJECT_VALUE", "REJECT_SAMPLE_VALUE": + opt := &ast.ExternalTableLiteralOrIdentifierOption{ + Value: &ast.IdentifierOrValueExpression{}, + } + if optName == "REJECT_VALUE" { + opt.OptionKind = "RejectValue" + } else { + opt.OptionKind = "RejectSampleValue" + } + // Parse numeric or integer literal + if p.curTok.Type == TokenNumber { + if strings.Contains(p.curTok.Literal, ".") { + numLit := &ast.NumericLiteral{LiteralType: "Numeric", Value: p.curTok.Literal} + opt.Value.Value = p.curTok.Literal + opt.Value.ValueExpression = numLit + } else { + intLit := &ast.IntegerLiteral{LiteralType: "Integer", Value: p.curTok.Literal} + opt.Value.Value = p.curTok.Literal + opt.Value.ValueExpression = intLit + } + p.nextToken() + } + stmt.ExternalTableOptions = append(stmt.ExternalTableOptions, opt) case "LOCATION", "FILE_FORMAT", "TABLE_OPTIONS": opt := &ast.ExternalTableLiteralOrIdentifierOption{ Value: &ast.IdentifierOrValueExpression{}, @@ -7270,6 +8258,16 @@ func (p *Parser) parseCreateExternalTableStatement() (*ast.CreateExternalTableSt } } + // Parse optional AS SELECT (CTAS syntax) + if p.curTok.Type == TokenAs { + p.nextToken() // consume AS + selectStmt, err := p.parseSelectStatement() + if err != nil { + return nil, err + } + stmt.SelectStatement = selectStmt + } + if p.curTok.Type == TokenSemicolon { p.nextToken() } @@ -8253,6 +9251,15 @@ func (p *Parser) parseCreatePartitionSchemeStatementFromPartition() (*ast.Create func (p *Parser) parseCreateDatabaseStatement() (ast.Statement, error) { p.nextToken() // consume DATABASE + // Check for DATABASE AUDIT SPECIFICATION + if strings.ToUpper(p.curTok.Literal) == "AUDIT" { + p.nextToken() // consume AUDIT + if strings.ToUpper(p.curTok.Literal) == "SPECIFICATION" { + p.nextToken() // consume SPECIFICATION + return p.parseCreateDatabaseAuditSpecificationStatement() + } + } + // Check for DATABASE ENCRYPTION KEY if strings.ToUpper(p.curTok.Literal) == "ENCRYPTION" { return p.parseCreateDatabaseEncryptionKeyStatement() @@ -8324,6 +9331,19 @@ func (p *Parser) parseCreateDatabaseStatement() (ast.Statement, error) { } multiPart.Count = len(multiPart.Identifiers) stmt.CopyOf = multiPart + + // Check for Azure-style options after COPY OF + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + opts, err := p.parseAzureDatabaseOptions() + if err != nil { + return nil, err + } + stmt.Options = append(stmt.Options, opts...) + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } } } } @@ -8367,6 +9387,21 @@ func (p *Parser) parseCreateDatabaseStatement() (ast.Statement, error) { case "ATTACH_REBUILD_LOG": stmt.AttachMode = "AttachRebuildLog" p.nextToken() + case "ATTACH_FORCE_REBUILD_LOG": + stmt.AttachMode = "AttachForceRebuildLog" + p.nextToken() + } + } + + // Check for AS SNAPSHOT OF clause + if strings.ToUpper(p.curTok.Literal) == "AS" { + p.nextToken() // consume AS + if strings.ToUpper(p.curTok.Literal) == "SNAPSHOT" { + p.nextToken() // consume SNAPSHOT + if strings.ToUpper(p.curTok.Literal) == "OF" { + p.nextToken() // consume OF + } + stmt.DatabaseSnapshot = p.parseIdentifier() } } @@ -8463,6 +9498,20 @@ func (p *Parser) parseCreateDatabaseOptions() ([]ast.CreateDatabaseOption, error } options = append(options, opt) + case "NEW_BROKER": + p.nextToken() // consume NEW_BROKER + opt := &ast.SimpleDatabaseOption{ + OptionKind: "NewBroker", + } + options = append(options, opt) + + case "ERROR_BROKER_CONVERSATIONS": + p.nextToken() // consume ERROR_BROKER_CONVERSATIONS + opt := &ast.SimpleDatabaseOption{ + OptionKind: "ErrorBrokerConversations", + } + options = append(options, opt) + case "NESTED_TRIGGERS": p.nextToken() // consume NESTED_TRIGGERS if p.curTok.Type == TokenEquals { @@ -8554,6 +9603,39 @@ func (p *Parser) parseAzureDatabaseOptions() ([]ast.CreateDatabaseOption, error) } options = append(options, opt) + case "SERVICE_OBJECTIVE": + // Check for elastic_pool(name = [epool1]) syntax + if strings.ToUpper(p.curTok.Literal) == "ELASTIC_POOL" { + p.nextToken() // consume ELASTIC_POOL + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + // Parse NAME = [poolname] + if strings.ToUpper(p.curTok.Literal) == "NAME" { + p.nextToken() // consume NAME + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + poolName := p.parseIdentifier() + opt := &ast.ElasticPoolSpecification{ + OptionKind: "ServiceObjective", + ElasticPoolName: poolName, + } + options = append(options, opt) + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } else { + // Parse service objective value (string literal) + value, _ := p.parseStringLiteral() + opt := &ast.LiteralDatabaseOption{ + OptionKind: "ServiceObjective", + Value: value, + } + options = append(options, opt) + } + default: // Skip unknown option value if p.curTok.Type != TokenComma && p.curTok.Type != TokenRParen { @@ -8805,6 +9887,13 @@ func (p *Parser) parseFileDeclarationOptions() ([]ast.FileDeclarationOption, err } opts = append(opts, opt) + case "OFFLINE": + p.nextToken() // consume OFFLINE + opt := &ast.SimpleFileDeclarationOption{ + OptionKind: "Offline", + } + opts = append(opts, opt) + default: // Unknown option, break return opts, nil @@ -9047,6 +10136,15 @@ func (p *Parser) parseCreateIndexStatement() (*ast.CreateIndexStatement, error) } } + // Parse WHERE filter_predicate + if strings.ToUpper(p.curTok.Literal) == "WHERE" { + p.nextToken() // consume WHERE + filterPred, err := p.parseBooleanExpression() + if err == nil { + stmt.FilterPredicate = filterPred + } + } + // Parse WITH (index options) if p.curTok.Type == TokenWith { p.nextToken() // consume WITH @@ -9067,6 +10165,20 @@ func (p *Parser) parseCreateIndexStatement() (*ast.CreateIndexStatement, error) stmt.OnFileGroupOrPartitionScheme = fg } + // Parse FILESTREAM_ON filegroup + if strings.ToUpper(p.curTok.Literal) == "FILESTREAM_ON" { + p.nextToken() // consume FILESTREAM_ON + value := p.curTok.Literal + stmt.FileStreamOn = &ast.IdentifierOrValueExpression{ + Value: value, + Identifier: &ast.Identifier{ + Value: value, + QuoteType: "NotQuoted", + }, + } + p.nextToken() + } + return stmt, nil } @@ -9156,6 +10268,57 @@ func (p *Parser) parseCreateIndexOptions() []ast.IndexOption { p.nextToken() // consume MINUTES } options = append(options, opt) + case "DATA_COMPRESSION": + // Parse DATA_COMPRESSION = level [ON PARTITIONS(range)] + compressionLevel := "None" + switch valueStr { + case "NONE": + compressionLevel = "None" + case "ROW": + compressionLevel = "Row" + case "PAGE": + compressionLevel = "Page" + case "COLUMNSTORE": + compressionLevel = "ColumnStore" + case "COLUMNSTORE_ARCHIVE": + compressionLevel = "ColumnStoreArchive" + } + opt := &ast.DataCompressionOption{ + CompressionLevel: compressionLevel, + OptionKind: "DataCompression", + } + // Check for optional ON PARTITIONS(range) + if p.curTok.Type == TokenOn { + p.nextToken() // consume ON + if strings.ToUpper(p.curTok.Literal) == "PARTITIONS" { + p.nextToken() // consume PARTITIONS + if p.curTok.Type == TokenLParen { + p.nextToken() // consume ( + for p.curTok.Type != TokenRParen && p.curTok.Type != TokenEOF { + partRange := &ast.CompressionPartitionRange{} + // Parse From value + partRange.From = &ast.IntegerLiteral{LiteralType: "Integer", Value: p.curTok.Literal} + p.nextToken() + // Check for TO keyword indicating a range + if strings.ToUpper(p.curTok.Literal) == "TO" { + p.nextToken() // consume TO + partRange.To = &ast.IntegerLiteral{LiteralType: "Integer", Value: p.curTok.Literal} + p.nextToken() + } + opt.PartitionRanges = append(opt.PartitionRanges, partRange) + if p.curTok.Type == TokenComma { + p.nextToken() + } else { + break + } + } + if p.curTok.Type == TokenRParen { + p.nextToken() // consume ) + } + } + } + } + options = append(options, opt) default: // Generic handling for other options if valueStr == "ON" || valueStr == "OFF" { @@ -9571,10 +10734,50 @@ func (p *Parser) parseCreateAsymmetricKeyStatement() (*ast.CreateAsymmetricKeySt Name: p.parseIdentifier(), } - // Check for FROM PROVIDER + // Check for AUTHORIZATION clause + if strings.ToUpper(p.curTok.Literal) == "AUTHORIZATION" { + p.nextToken() // consume AUTHORIZATION + stmt.Owner = p.parseIdentifier() + } + + // Check for FROM clause (FILE, EXECUTABLE FILE, ASSEMBLY, PROVIDER) if p.curTok.Type == TokenFrom { p.nextToken() // consume FROM - if strings.ToUpper(p.curTok.Literal) == "PROVIDER" { + fromType := strings.ToUpper(p.curTok.Literal) + switch fromType { + case "FILE": + p.nextToken() // consume FILE + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + file, _ := p.parseStringLiteral() + stmt.KeySource = &ast.FileEncryptionSource{ + IsExecutable: false, + File: file, + } + stmt.EncryptionAlgorithm = "None" + case "EXECUTABLE": + p.nextToken() // consume EXECUTABLE + if strings.ToUpper(p.curTok.Literal) == "FILE" { + p.nextToken() // consume FILE + } + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + file, _ := p.parseStringLiteral() + stmt.KeySource = &ast.FileEncryptionSource{ + IsExecutable: true, + File: file, + } + stmt.EncryptionAlgorithm = "None" + case "ASSEMBLY": + p.nextToken() // consume ASSEMBLY + assemblyName := p.parseIdentifier() + stmt.KeySource = &ast.AssemblyEncryptionSource{ + Assembly: assemblyName, + } + stmt.EncryptionAlgorithm = "None" + case "PROVIDER": p.nextToken() // consume PROVIDER source := &ast.ProviderEncryptionSource{ Name: p.parseIdentifier(), @@ -9593,28 +10796,7 @@ func (p *Parser) parseCreateAsymmetricKeyStatement() (*ast.CreateAsymmetricKeySt p.nextToken() // consume = } alg := strings.ToUpper(p.curTok.Literal) - // Map algorithm names to proper case - algMap := map[string]string{ - "DES": "Des", - "RC2": "RC2", - "RC4": "RC4", - "RC4_128": "RC4_128", - "TRIPLE_DES": "TripleDes", - "AES_128": "Aes128", - "AES_192": "Aes192", - "AES_256": "Aes256", - "RSA_512": "Rsa512", - "RSA_1024": "Rsa1024", - "RSA_2048": "Rsa2048", - "RSA_3072": "Rsa3072", - "RSA_4096": "Rsa4096", - "DESX": "DesX", - "TRIPLE_DES_3KEY": "TripleDes3Key", - } - mappedAlg := alg - if mapped, ok := algMap[alg]; ok { - mappedAlg = mapped - } + mappedAlg := p.mapEncryptionAlgorithm(alg) source.KeyOptions = append(source.KeyOptions, &ast.AlgorithmKeyOption{ Algorithm: mappedAlg, OptionKind: "Algorithm", @@ -9642,7 +10824,7 @@ func (p *Parser) parseCreateAsymmetricKeyStatement() (*ast.CreateAsymmetricKeySt }) p.nextToken() default: - goto doneWithOptions + goto doneWithProviderOptions } if p.curTok.Type == TokenComma { @@ -9653,12 +10835,26 @@ func (p *Parser) parseCreateAsymmetricKeyStatement() (*ast.CreateAsymmetricKeySt break } } - doneWithOptions: + doneWithProviderOptions: } stmt.KeySource = source } } + // Check for WITH ALGORITHM = ... (without FROM clause) + if p.curTok.Type == TokenWith { + p.nextToken() // consume WITH + if strings.ToUpper(p.curTok.Literal) == "ALGORITHM" { + p.nextToken() // consume ALGORITHM + if p.curTok.Type == TokenEquals { + p.nextToken() // consume = + } + alg := strings.ToUpper(p.curTok.Literal) + stmt.EncryptionAlgorithm = p.mapEncryptionAlgorithm(alg) + p.nextToken() + } + } + // Check for ENCRYPTION BY PASSWORD if strings.ToUpper(p.curTok.Literal) == "ENCRYPTION" { p.nextToken() // consume ENCRYPTION @@ -9682,6 +10878,31 @@ func (p *Parser) parseCreateAsymmetricKeyStatement() (*ast.CreateAsymmetricKeySt return stmt, nil } +// mapEncryptionAlgorithm maps SQL encryption algorithm names to proper case +func (p *Parser) mapEncryptionAlgorithm(alg string) string { + algMap := map[string]string{ + "DES": "Des", + "RC2": "RC2", + "RC4": "RC4", + "RC4_128": "RC4_128", + "TRIPLE_DES": "TripleDes", + "AES_128": "Aes128", + "AES_192": "Aes192", + "AES_256": "Aes256", + "RSA_512": "Rsa512", + "RSA_1024": "Rsa1024", + "RSA_2048": "Rsa2048", + "RSA_3072": "Rsa3072", + "RSA_4096": "Rsa4096", + "DESX": "DesX", + "TRIPLE_DES_3KEY": "TripleDes3Key", + } + if mapped, ok := algMap[alg]; ok { + return mapped + } + return alg +} + func (p *Parser) parseCreateSymmetricKeyStatement() (*ast.CreateSymmetricKeyStatement, error) { p.nextToken() // consume SYMMETRIC if strings.ToUpper(p.curTok.Literal) == "KEY" { diff --git a/parser/testdata/AlterDatabaseOptionsTests/metadata.json b/parser/testdata/AlterDatabaseOptionsTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AlterDatabaseOptionsTests/metadata.json +++ b/parser/testdata/AlterDatabaseOptionsTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AlterDatabaseOptionsTests90/metadata.json b/parser/testdata/AlterDatabaseOptionsTests90/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AlterDatabaseOptionsTests90/metadata.json +++ b/parser/testdata/AlterDatabaseOptionsTests90/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AlterExternalResourcePoolStatementTests130/metadata.json b/parser/testdata/AlterExternalResourcePoolStatementTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AlterExternalResourcePoolStatementTests130/metadata.json +++ b/parser/testdata/AlterExternalResourcePoolStatementTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AlterProcedureStatementTests120/metadata.json b/parser/testdata/AlterProcedureStatementTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AlterProcedureStatementTests120/metadata.json +++ b/parser/testdata/AlterProcedureStatementTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AlterTableAddTableElementStatementTests/metadata.json b/parser/testdata/AlterTableAddTableElementStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AlterTableAddTableElementStatementTests/metadata.json +++ b/parser/testdata/AlterTableAddTableElementStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AlterTableAlterColumnStatementTests140/metadata.json b/parser/testdata/AlterTableAlterColumnStatementTests140/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AlterTableAlterColumnStatementTests140/metadata.json +++ b/parser/testdata/AlterTableAlterColumnStatementTests140/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AlterTableStatementTests100/metadata.json b/parser/testdata/AlterTableStatementTests100/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AlterTableStatementTests100/metadata.json +++ b/parser/testdata/AlterTableStatementTests100/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AsymmetricKeyStatementTests/metadata.json b/parser/testdata/AsymmetricKeyStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AsymmetricKeyStatementTests/metadata.json +++ b/parser/testdata/AsymmetricKeyStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/AuditSpecificationStatementTests110/metadata.json b/parser/testdata/AuditSpecificationStatementTests110/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/AuditSpecificationStatementTests110/metadata.json +++ b/parser/testdata/AuditSpecificationStatementTests110/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/BackupStatementTests120/metadata.json b/parser/testdata/BackupStatementTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/BackupStatementTests120/metadata.json +++ b/parser/testdata/BackupStatementTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines100_AlterTableStatementTests100/metadata.json b/parser/testdata/Baselines100_AlterTableStatementTests100/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines100_AlterTableStatementTests100/metadata.json +++ b/parser/testdata/Baselines100_AlterTableStatementTests100/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines100_CreateAlterDropResourcePoolStatementTests/metadata.json b/parser/testdata/Baselines100_CreateAlterDropResourcePoolStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines100_CreateAlterDropResourcePoolStatementTests/metadata.json +++ b/parser/testdata/Baselines100_CreateAlterDropResourcePoolStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines100_CreateIndexStatementTests100/metadata.json b/parser/testdata/Baselines100_CreateIndexStatementTests100/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines100_CreateIndexStatementTests100/metadata.json +++ b/parser/testdata/Baselines100_CreateIndexStatementTests100/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines110_AuditSpecificationStatementTests110/metadata.json b/parser/testdata/Baselines110_AuditSpecificationStatementTests110/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines110_AuditSpecificationStatementTests110/metadata.json +++ b/parser/testdata/Baselines110_AuditSpecificationStatementTests110/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines110_CreateAvailabilityGroupStatementTests/metadata.json b/parser/testdata/Baselines110_CreateAvailabilityGroupStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines110_CreateAvailabilityGroupStatementTests/metadata.json +++ b/parser/testdata/Baselines110_CreateAvailabilityGroupStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines110_ExpressionTests110/metadata.json b/parser/testdata/Baselines110_ExpressionTests110/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines110_ExpressionTests110/metadata.json +++ b/parser/testdata/Baselines110_ExpressionTests110/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines120_AlterProcedureStatementTests120/metadata.json b/parser/testdata/Baselines120_AlterProcedureStatementTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines120_AlterProcedureStatementTests120/metadata.json +++ b/parser/testdata/Baselines120_AlterProcedureStatementTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines120_BackupStatementTests120/metadata.json b/parser/testdata/Baselines120_BackupStatementTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines120_BackupStatementTests120/metadata.json +++ b/parser/testdata/Baselines120_BackupStatementTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines120_ColumnDefinitionTests120/metadata.json b/parser/testdata/Baselines120_ColumnDefinitionTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines120_ColumnDefinitionTests120/metadata.json +++ b/parser/testdata/Baselines120_ColumnDefinitionTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines120_CreateAlterDatabaseStatementTestsAzure120/metadata.json b/parser/testdata/Baselines120_CreateAlterDatabaseStatementTestsAzure120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines120_CreateAlterDatabaseStatementTestsAzure120/metadata.json +++ b/parser/testdata/Baselines120_CreateAlterDatabaseStatementTestsAzure120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines120_CreateProcedureStatementTests120/metadata.json b/parser/testdata/Baselines120_CreateProcedureStatementTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines120_CreateProcedureStatementTests120/metadata.json +++ b/parser/testdata/Baselines120_CreateProcedureStatementTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines130_AlterExternalResourcePoolStatementTests130/metadata.json b/parser/testdata/Baselines130_AlterExternalResourcePoolStatementTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines130_AlterExternalResourcePoolStatementTests130/metadata.json +++ b/parser/testdata/Baselines130_AlterExternalResourcePoolStatementTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines130_ColumnStoreInlineIndex130/metadata.json b/parser/testdata/Baselines130_ColumnStoreInlineIndex130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines130_ColumnStoreInlineIndex130/metadata.json +++ b/parser/testdata/Baselines130_ColumnStoreInlineIndex130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines130_DropIfExistsTests130/metadata.json b/parser/testdata/Baselines130_DropIfExistsTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines130_DropIfExistsTests130/metadata.json +++ b/parser/testdata/Baselines130_DropIfExistsTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines130_ExternalTableCtasStatementTests/metadata.json b/parser/testdata/Baselines130_ExternalTableCtasStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines130_ExternalTableCtasStatementTests/metadata.json +++ b/parser/testdata/Baselines130_ExternalTableCtasStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines130_NativelyCompiledScalarUDFTests130/metadata.json b/parser/testdata/Baselines130_NativelyCompiledScalarUDFTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines130_NativelyCompiledScalarUDFTests130/metadata.json +++ b/parser/testdata/Baselines130_NativelyCompiledScalarUDFTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines130_RemoteDataArchiveTableTests130/metadata.json b/parser/testdata/Baselines130_RemoteDataArchiveTableTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines130_RemoteDataArchiveTableTests130/metadata.json +++ b/parser/testdata/Baselines130_RemoteDataArchiveTableTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines130_UniqueInlineIndex130/metadata.json b/parser/testdata/Baselines130_UniqueInlineIndex130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines130_UniqueInlineIndex130/metadata.json +++ b/parser/testdata/Baselines130_UniqueInlineIndex130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines140_AlterTableAlterColumnStatementTests140/metadata.json b/parser/testdata/Baselines140_AlterTableAlterColumnStatementTests140/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines140_AlterTableAlterColumnStatementTests140/metadata.json +++ b/parser/testdata/Baselines140_AlterTableAlterColumnStatementTests140/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines150_ServerAuditStatementTests150/metadata.json b/parser/testdata/Baselines150_ServerAuditStatementTests150/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines150_ServerAuditStatementTests150/metadata.json +++ b/parser/testdata/Baselines150_ServerAuditStatementTests150/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines160_SelectStatementTests160/metadata.json b/parser/testdata/Baselines160_SelectStatementTests160/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines160_SelectStatementTests160/metadata.json +++ b/parser/testdata/Baselines160_SelectStatementTests160/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines80_AlterTableAddTableElementStatementTests/metadata.json b/parser/testdata/Baselines80_AlterTableAddTableElementStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines80_AlterTableAddTableElementStatementTests/metadata.json +++ b/parser/testdata/Baselines80_AlterTableAddTableElementStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines80_SecurityStatement80Tests/metadata.json b/parser/testdata/Baselines80_SecurityStatement80Tests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines80_SecurityStatement80Tests/metadata.json +++ b/parser/testdata/Baselines80_SecurityStatement80Tests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines80_ViewStatementTests/metadata.json b/parser/testdata/Baselines80_ViewStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines80_ViewStatementTests/metadata.json +++ b/parser/testdata/Baselines80_ViewStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines90_AlterCreateDatabaseStatementTests90/metadata.json b/parser/testdata/Baselines90_AlterCreateDatabaseStatementTests90/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines90_AlterCreateDatabaseStatementTests90/metadata.json +++ b/parser/testdata/Baselines90_AlterCreateDatabaseStatementTests90/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines90_AlterDatabaseOptionsTests90/metadata.json b/parser/testdata/Baselines90_AlterDatabaseOptionsTests90/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines90_AlterDatabaseOptionsTests90/metadata.json +++ b/parser/testdata/Baselines90_AlterDatabaseOptionsTests90/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines90_AlterTableAddTableElementStatementTests/metadata.json b/parser/testdata/Baselines90_AlterTableAddTableElementStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines90_AlterTableAddTableElementStatementTests/metadata.json +++ b/parser/testdata/Baselines90_AlterTableAddTableElementStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines90_AsymmetricKeyStatementTests/metadata.json b/parser/testdata/Baselines90_AsymmetricKeyStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines90_AsymmetricKeyStatementTests/metadata.json +++ b/parser/testdata/Baselines90_AsymmetricKeyStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines90_CreateFunctionStatementTests90/metadata.json b/parser/testdata/Baselines90_CreateFunctionStatementTests90/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines90_CreateFunctionStatementTests90/metadata.json +++ b/parser/testdata/Baselines90_CreateFunctionStatementTests90/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines90_SecurityStatement80Tests/metadata.json b/parser/testdata/Baselines90_SecurityStatement80Tests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines90_SecurityStatement80Tests/metadata.json +++ b/parser/testdata/Baselines90_SecurityStatement80Tests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/Baselines90_ViewStatementTests/metadata.json b/parser/testdata/Baselines90_ViewStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/Baselines90_ViewStatementTests/metadata.json +++ b/parser/testdata/Baselines90_ViewStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/BaselinesCommon_AlterDatabaseOptionsTests/metadata.json b/parser/testdata/BaselinesCommon_AlterDatabaseOptionsTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/BaselinesCommon_AlterDatabaseOptionsTests/metadata.json +++ b/parser/testdata/BaselinesCommon_AlterDatabaseOptionsTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/BaselinesCommon_ForeignKeyConstraintTests/metadata.json b/parser/testdata/BaselinesCommon_ForeignKeyConstraintTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/BaselinesCommon_ForeignKeyConstraintTests/metadata.json +++ b/parser/testdata/BaselinesCommon_ForeignKeyConstraintTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/BaselinesCommon_FunctionStatementTests/metadata.json b/parser/testdata/BaselinesCommon_FunctionStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/BaselinesCommon_FunctionStatementTests/metadata.json +++ b/parser/testdata/BaselinesCommon_FunctionStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/BaselinesFabricDW_ScalarFunctionTestsFabricDW/metadata.json b/parser/testdata/BaselinesFabricDW_ScalarFunctionTestsFabricDW/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/BaselinesFabricDW_ScalarFunctionTestsFabricDW/metadata.json +++ b/parser/testdata/BaselinesFabricDW_ScalarFunctionTestsFabricDW/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ColumnDefinitionTests120/metadata.json b/parser/testdata/ColumnDefinitionTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ColumnDefinitionTests120/metadata.json +++ b/parser/testdata/ColumnDefinitionTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ColumnStoreInlineIndex130/metadata.json b/parser/testdata/ColumnStoreInlineIndex130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ColumnStoreInlineIndex130/metadata.json +++ b/parser/testdata/ColumnStoreInlineIndex130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/CreateAlterDatabaseStatementTestsAzure120/metadata.json b/parser/testdata/CreateAlterDatabaseStatementTestsAzure120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/CreateAlterDatabaseStatementTestsAzure120/metadata.json +++ b/parser/testdata/CreateAlterDatabaseStatementTestsAzure120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/CreateAlterDropResourcePoolStatementTests/metadata.json b/parser/testdata/CreateAlterDropResourcePoolStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/CreateAlterDropResourcePoolStatementTests/metadata.json +++ b/parser/testdata/CreateAlterDropResourcePoolStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/CreateAvailabilityGroupStatementTests/metadata.json b/parser/testdata/CreateAvailabilityGroupStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/CreateAvailabilityGroupStatementTests/metadata.json +++ b/parser/testdata/CreateAvailabilityGroupStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/CreateFunctionStatementTests90/metadata.json b/parser/testdata/CreateFunctionStatementTests90/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/CreateFunctionStatementTests90/metadata.json +++ b/parser/testdata/CreateFunctionStatementTests90/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/CreateIndexStatementTests100/metadata.json b/parser/testdata/CreateIndexStatementTests100/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/CreateIndexStatementTests100/metadata.json +++ b/parser/testdata/CreateIndexStatementTests100/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/CreateProcedureStatementTests120/metadata.json b/parser/testdata/CreateProcedureStatementTests120/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/CreateProcedureStatementTests120/metadata.json +++ b/parser/testdata/CreateProcedureStatementTests120/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/DropIfExistsTests130/metadata.json b/parser/testdata/DropIfExistsTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/DropIfExistsTests130/metadata.json +++ b/parser/testdata/DropIfExistsTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ExpressionTests110/metadata.json b/parser/testdata/ExpressionTests110/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ExpressionTests110/metadata.json +++ b/parser/testdata/ExpressionTests110/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ExternalTableCtasStatementTests/metadata.json b/parser/testdata/ExternalTableCtasStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ExternalTableCtasStatementTests/metadata.json +++ b/parser/testdata/ExternalTableCtasStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ForeignKeyConstraintTests/metadata.json b/parser/testdata/ForeignKeyConstraintTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ForeignKeyConstraintTests/metadata.json +++ b/parser/testdata/ForeignKeyConstraintTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/NativelyCompiledScalarUDFTests130/metadata.json b/parser/testdata/NativelyCompiledScalarUDFTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/NativelyCompiledScalarUDFTests130/metadata.json +++ b/parser/testdata/NativelyCompiledScalarUDFTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/ast.json index 8dd49b5d..870b015b 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/ast.json @@ -10,7 +10,8 @@ "$type": "Identifier", "QuoteType": "NotQuoted", "Value": "tempdb" - } + }, + "UseCurrent": false } ] } diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/metadata.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/metadata.json index ef120d97..0967ef42 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/metadata.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFileStatementTest/metadata.json @@ -1 +1 @@ -{"todo": true} +{} diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json index 66a45ef8..d4819c54 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/ast.json @@ -17,12 +17,13 @@ "Value": "fg1" }, "MakeDefault": false, - "UseCurrent": false, "NewFileGroupName": { "$type": "Identifier", "QuoteType": "NotQuoted", "Value": "SomeIdent" - } + }, + "UpdatabilityOption": "None", + "UseCurrent": false } ] } diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/metadata.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/metadata.json index ef120d97..0967ef42 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/metadata.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup2StatementTest/metadata.json @@ -1 +1 @@ -{"todo": true} +{} diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json index a229990c..db032cfc 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/ast.json @@ -17,6 +17,7 @@ "Value": "fg1" }, "MakeDefault": true, + "UpdatabilityOption": "None", "UseCurrent": false } ] diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/metadata.json b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/metadata.json index ef120d97..0967ef42 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/metadata.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyFilegroup3StatementTest/metadata.json @@ -1 +1 @@ -{"todo": true} +{} diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/ast.json index df514a7e..35cce9a3 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/ast.json @@ -15,7 +15,8 @@ "$type": "Identifier", "QuoteType": "NotQuoted", "Value": "newName" - } + }, + "UseCurrent": false } ] } diff --git a/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/metadata.json b/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/metadata.json index ef120d97..0967ef42 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/metadata.json +++ b/parser/testdata/PhaseOne_AlterDatabaseModifyNameStatementTest/metadata.json @@ -1 +1 @@ -{"todo": true} +{} diff --git a/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/ast.json b/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/ast.json index 9db193b1..4f26fde8 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/ast.json +++ b/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/ast.json @@ -15,7 +15,8 @@ "$type": "Identifier", "QuoteType": "SquareBracket", "Value": "file1" - } + }, + "UseCurrent": false } ] } diff --git a/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/metadata.json b/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/metadata.json index ef120d97..0967ef42 100644 --- a/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/metadata.json +++ b/parser/testdata/PhaseOne_AlterDatabaseRemoveFileStatementTest/metadata.json @@ -1 +1 @@ -{"todo": true} +{} diff --git a/parser/testdata/PhaseOne_CreateFulltextCatalog/ast.json b/parser/testdata/PhaseOne_CreateFulltextCatalog/ast.json index 73b61ee0..acded200 100644 --- a/parser/testdata/PhaseOne_CreateFulltextCatalog/ast.json +++ b/parser/testdata/PhaseOne_CreateFulltextCatalog/ast.json @@ -5,7 +5,8 @@ "$type": "TSqlBatch", "Statements": [ { - "$type": "CreateFulltextCatalogStatement", + "$type": "CreateFullTextCatalogStatement", + "IsDefault": false, "Name": { "$type": "Identifier", "QuoteType": "NotQuoted", diff --git a/parser/testdata/PhaseOne_CreateFulltextCatalog/metadata.json b/parser/testdata/PhaseOne_CreateFulltextCatalog/metadata.json index a989cc0a..9e26dfee 100644 --- a/parser/testdata/PhaseOne_CreateFulltextCatalog/metadata.json +++ b/parser/testdata/PhaseOne_CreateFulltextCatalog/metadata.json @@ -1 +1 @@ -{"invalid_syntax": true} +{} \ No newline at end of file diff --git a/parser/testdata/RemoteDataArchiveTableTests130/metadata.json b/parser/testdata/RemoteDataArchiveTableTests130/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/RemoteDataArchiveTableTests130/metadata.json +++ b/parser/testdata/RemoteDataArchiveTableTests130/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ScalarFunctionTestsFabricDW/metadata.json b/parser/testdata/ScalarFunctionTestsFabricDW/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ScalarFunctionTestsFabricDW/metadata.json +++ b/parser/testdata/ScalarFunctionTestsFabricDW/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/SecurityStatement80Tests/metadata.json b/parser/testdata/SecurityStatement80Tests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/SecurityStatement80Tests/metadata.json +++ b/parser/testdata/SecurityStatement80Tests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/SelectExpressionTests/metadata.json b/parser/testdata/SelectExpressionTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/SelectExpressionTests/metadata.json +++ b/parser/testdata/SelectExpressionTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/SelectStatementTests160/metadata.json b/parser/testdata/SelectStatementTests160/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/SelectStatementTests160/metadata.json +++ b/parser/testdata/SelectStatementTests160/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ServerAuditStatementTests150/metadata.json b/parser/testdata/ServerAuditStatementTests150/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ServerAuditStatementTests150/metadata.json +++ b/parser/testdata/ServerAuditStatementTests150/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{} diff --git a/parser/testdata/ViewStatementTests/metadata.json b/parser/testdata/ViewStatementTests/metadata.json index ccffb5b9..0967ef42 100644 --- a/parser/testdata/ViewStatementTests/metadata.json +++ b/parser/testdata/ViewStatementTests/metadata.json @@ -1 +1 @@ -{"todo": true} \ No newline at end of file +{}