JasperFx / weasel

Database Development Made Easy for .Net
MIT License
65 stars 19 forks source link

`sp_MSdropconstraints` doesn't exist on Azure SQL #118

Open meinsiedler opened 5 months ago

meinsiedler commented 5 months ago

Hi!

We are using Wolverine with SQL Server Message Persistence with an Azure SQL database.

When Wolverine creates the required tables, we are receiving a SQLException because sp_MSdropconstraints is called when dropping and creating the missing tables and this stored procedure is not available in Azure SQL.

IF NOT EXISTS ( SELECT  *
                FROM    sys.schemas
                WHERE   name = N'wolverine' )
    EXEC('CREATE SCHEMA [wolverine]');

EXEC sp_MSdropconstraints 'wolverine_incoming_envelopes', 'wolverine';
DROP TABLE IF EXISTS wolverine.wolverine_incoming_envelopes;
CREATE TABLE wolverine.wolverine_incoming_envelopes (
    id                uniqueidentifier    NOT NULL,
    status            varchar(25)         NOT NULL,
    owner_id          int                 NOT NULL,
    execution_time    datetimeoffset      NULL DEFAULT NULL,
    attempts          int                 NULL DEFAULT 0,
    body              varbinary(max)      NOT NULL,
    message_type      varchar(250)        NOT NULL,
    received_at       varchar(250)        NULL,
    keep_until        datetimeoffset      NULL,
CONSTRAINT pkey_wolverine_incoming_envelopes_id PRIMARY KEY (id)
);

fail: Wolverine.Runtime.WolverineRuntime[0]
      Failed to start the Wolverine messaging
      Microsoft.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'sp_MSdropconstraints'.
         at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
         at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
         at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
         at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
         at Microsoft.Data.SqlClient.SqlCommand.InternalEndExecuteNonQuery(IAsyncResult asyncResult, Boolean isInternal, String endMethod)
         at Microsoft.Data.SqlClient.SqlCommand.EndExecuteNonQueryInternal(IAsyncResult asyncResult)
         at Microsoft.Data.SqlClient.SqlCommand.EndExecuteNonQueryAsync(IAsyncResult asyncResult)
         at Microsoft.Data.SqlClient.SqlCommand.<>c.<InternalExecuteNonQueryAsync>b__210_1(IAsyncResult result)
         at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
      --- End of stack trace from previous location ---
         at Weasel.SqlServer.SqlServerMigrator.executeCommand(DbConnection conn, IMigrationLogger logger, StringWriter writer, CancellationToken ct)
         at Weasel.SqlServer.SqlServerMigrator.executeDelta(SchemaMigration migration, DbConnection conn, AutoCreate autoCreate, IMigrationLogger logger, CancellationToken ct)
         at Weasel.Core.Migrator.ApplyAllAsync(DbConnection conn, SchemaMigration migration, AutoCreate autoCreate, IMigrationLogger logger, CancellationToken ct)
         at Wolverine.RDBMS.MessageDatabase`1.migrateAsync(DbConnection conn)
         at Wolverine.RDBMS.MessageDatabase`1.<MigrateAsync>b__4_0()
         at Wolverine.RDBMS.MessageDatabase`1.<MigrateAsync>b__4_0()
         at Wolverine.RDBMS.MessageDatabase`1.MigrateAsync()
         at Wolverine.Runtime.WolverineRuntime.StartAsync(CancellationToken cancellationToken)
      ClientConnectionId:90dce724-60c7-4b23-8e9e-5e8c808dbcc1
      Error Number:2812,State:62,Class:16

I figured out that this comes from Weasel, where I found the following line:

https://github.com/JasperFx/weasel/blob/d5c3ad1e254b55882ca0ebf62b79a221febe30db/src/Weasel.SqlServer/Tables/Table.cs#L67

I couldn't find much documentation for sp_MSdropconstraints, except this source code. It seems like this stored procedure is part of the Merge Replication Feature which is only available in SQL Server (but not in Azure SQL).

Would it be possible to replace the sp_MSdropconstraints with native SQL code for dropping the foreign key constraints? The sp_MSdropconstraints basically does exactly that according to the linked source code.

I would also be happy to provide a PR with that change, but I would need some help regarding the release and bringing the version to Wolverine.

mysticmind commented 3 months ago

It would be better to have our own version of sp_MSdropconstraints. I have made few edits/tweaks to the stored procedure code and have outlined it as below:

create procedure weasel_dropconstraints  
    @table sysname,  
    @owner sysname = null  
as  
    declare @const_name nvarchar(258)  
    declare @objid int  
    declare @retcode int  
    declare @qualified_tablename   nvarchar(517)  
    declare @quoted_tablename   nvarchar(270)  
    declare @quoted_ownername   nvarchar(270)  

    select @quoted_tablename = quotename(@table)  

    if @owner is not NULL  
    begin  
        set @quoted_ownername = QUOTENAME(@owner)  
        set @qualified_tablename= @quoted_ownername + '.' + @quoted_tablename  
    end  
    else  
        set @qualified_tablename= @quoted_tablename  

    set @objid = object_id(@qualified_tablename)  
    if @objid is null  
    begin  
        if @owner is null  
        begin  
            select @objid = object_id from sys.objects   
                where name=@quoted_tablename  
        end  
        else  
        begin  
            select @objid = object_id from sys.objects   
                where name=@quoted_tablename and schema_name(schema_id)=@quoted_ownername  
        end  
    end  
    if @objid is NULL  
        return (1)  

    select @const_name = QUOTENAME(object_name(object_id)) from   
        sys.foreign_keys where parent_object_id = @objid  

    while @const_name is not null  
    begin  
        exec ('alter table ' + @qualified_tablename +  
            ' drop constraint ' + @const_name)  
        if @@ERROR <> 0  
            return (1)  
        set @const_name = NULL  
        select @const_name = QUOTENAME(object_name(object_id)) from   
            sys.foreign_keys where parent_object_id = @objid  
    end  

    return (0) 

I will discuss with @jeremydmiller and get this going.