Skip to content

Commit

Permalink
Merge branch 'release/4.1.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholasbarlow committed Jan 31, 2025
2 parents e30ae84 + ae01fc9 commit 4466a0d
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 14 deletions.
6 changes: 6 additions & 0 deletions docs/4.1.2-release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
### Features
- Validate that configure sql schema exists in verify connection

### Fixes
- Fallback to default schema if empty string is specified
- Fix issue where entityreferences properties were not serialized correctly
2 changes: 2 additions & 0 deletions src/Connector.SqlServer/Connector/ISqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface ISqlClient
{
bool VerifyConnectionProperties(IReadOnlyDictionary<string, object> config, out ConnectionConfigurationError configurationError);

Task<bool> VerifySchemaExists(SqlTransaction transaction, string schema);

Task<SqlConnection> BeginConnection(IReadOnlyDictionary<string, object> config);

Task<DataTable> GetTableColumns(SqlConnection connection, string tableName, string schema);
Expand Down
18 changes: 18 additions & 0 deletions src/Connector.SqlServer/Connector/SqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public string BuildConnectionString(IReadOnlyDictionary<string, object> config)
DataSource = (string)config[SqlServerConstants.KeyName.Host],
InitialCatalog = (string)config[SqlServerConstants.KeyName.DatabaseName],
Pooling = true,
// Turn off unconditionally for now. Later maybe should be coming from configuration.
// Is needed as new SqlClient library encrypts by default.
Encrypt = false
};

// Configure port
Expand Down Expand Up @@ -93,6 +96,21 @@ public bool VerifyConnectionProperties(IReadOnlyDictionary<string, object> confi
return true;
}

public async Task<bool> VerifySchemaExists(SqlTransaction transaction, string schema)
{
// INFORMATION_SCHEMA.SCHEMATA contains all the views accessible to the current user in SQL Server.
var schemaQuery = $"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{schema}'";

var command = transaction.Connection.CreateCommand();
command.CommandText = schemaQuery;
command.Transaction = transaction;

await using (var reader = await command.ExecuteReaderAsync())
{
return reader.HasRows;
}
}

public async Task<SqlConnection> BeginConnection(IReadOnlyDictionary<string, object> config)
{
var connectionString = BuildConnectionString(config);
Expand Down
24 changes: 23 additions & 1 deletion src/Connector.SqlServer/Connector/SqlServerConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,31 @@ public override async Task<ConnectionVerificationResult> VerifyConnection(Execut

await using var connectionAndTransaction = await _client.BeginTransaction(configurationData);
var connectionIsOpen = connectionAndTransaction.Connection.State == ConnectionState.Open;

if (!connectionIsOpen)
{
_logger.LogError("SqlServerConnector connection verification failed, connection could not be opened");
return new ConnectionVerificationResult(false, "Connection could not be opened");
}

var schema = configurationData.GetValue(SqlServerConstants.KeyName.Schema, (string)null);
if (string.IsNullOrEmpty(schema))
{
schema = SqlTableName.DefaultSchema;
}


var schemaExists = await _client.VerifySchemaExists(connectionAndTransaction.Transaction, schema);

await connectionAndTransaction.DisposeAsync();

return new ConnectionVerificationResult(connectionIsOpen);
if (!schemaExists)
{
_logger.LogError("SqlServerConnector connection verification failed, schema '{schema}' does not exist", schema);
return new ConnectionVerificationResult(false, "Schema does not exist");
}

return new ConnectionVerificationResult(true);
}
catch (Exception e)
{
Expand Down
10 changes: 4 additions & 6 deletions src/Connector.SqlServer/Utils/ConnectorConnectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ internal static class ConnectorConnectionExtensions
/// </summary>
public static SqlName GetSchema(this IConnectorConnectionV2 config)
{
if (config.Authentication.TryGetValue(SqlServerConstants.KeyName.Schema, out var value) && value is string schema)
if (config.Authentication.TryGetValue(SqlServerConstants.KeyName.Schema, out var value) &&
value is string schema &&
!string.IsNullOrEmpty(schema))
{
var sanitizedSchema = schema.ToSanitizedSqlName();

if (!string.IsNullOrEmpty(sanitizedSchema))
{
return SqlName.FromSanitized(schema);
}
return SqlName.FromSanitized(sanitizedSchema);
}

return SqlName.FromSanitized(SqlTableName.DefaultSchema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ public static MainTableColumnDefinition[] GetColumnDefinitions(StreamMode stream
return entityTypeValue.ToString();
}

if (propertyValue is EntityReference entityReference)
{
var codeString = entityReference.Code?.ToString();
return !string.IsNullOrEmpty(codeString)
? codeString
: entityReference.Name;
}

return propertyValue;
},
CanBeNull: true);
Expand Down
10 changes: 5 additions & 5 deletions test/unit/Connector.SqlServer.Test/Connector/SqlClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void BuildConnectionString_Sets_From_Dictionary()

var result = _sut.BuildConnectionString(properties);

Assert.Equal("Data Source=host,1433;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Authentication=SqlPassword", result);
Assert.Equal("Data Source=host,1433;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Encrypt=False;Authentication=SqlPassword", result);
}

[Fact]
Expand All @@ -44,7 +44,7 @@ public void BuildConnectionString_WithPort_Sets_From_Dictionary()

var result = _sut.BuildConnectionString(properties);

Assert.Equal("Data Source=host,9499;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Authentication=SqlPassword", result);
Assert.Equal("Data Source=host,9499;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Encrypt=False;Authentication=SqlPassword", result);
}

[Fact]
Expand All @@ -61,7 +61,7 @@ public void BuildConnectionString_WithStringPort_Sets_From_Dictionary()

var result = _sut.BuildConnectionString(properties);

Assert.Equal("Data Source=host,9499;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Authentication=SqlPassword", result);
Assert.Equal("Data Source=host,9499;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Encrypt=False;Authentication=SqlPassword", result);
}

[Fact]
Expand All @@ -78,7 +78,7 @@ public void BuildConnectionString_WithInvalidPort_Sets_From_Dictionary()

var result = _sut.BuildConnectionString(properties);

Assert.Equal("Data Source=host,1433;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Authentication=SqlPassword", result);
Assert.Equal("Data Source=host,1433;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=200;Encrypt=False;Authentication=SqlPassword", result);
}

[Fact]
Expand All @@ -98,7 +98,7 @@ public void BuildConnectionString_WithConnectionPoolSize_Sets_From_Dictionary()
var result = _sut.BuildConnectionString(properties);

// assert
Assert.Equal("Data Source=host,1433;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=10;Authentication=SqlPassword", result);
Assert.Equal("Data Source=host,1433;Initial Catalog=database;User ID=user;Password=password;Pooling=True;Max Pool Size=10;Encrypt=False;Authentication=SqlPassword", result);
}

[Fact] public void VerifyConnectionProperties_WithValidProperties_ReturnsTrue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,62 @@ public void EntityTypePropertyValues_ShouldBeMadeIntoStrings(
var syncColumnDefinitions = MainTableDefinition.GetColumnDefinitions(StreamMode.Sync, properties);

// assert
var discoveryDateColumnDefinition = syncColumnDefinitions.Where(column => column.Name == "Type").Should().ContainSingle().And.Subject.First();
var sqlDateValue = discoveryDateColumnDefinition.GetValueFunc(sqlEntityTypePropertyDate);
var entityTypeColumnDefinition = syncColumnDefinitions.Where(column => column.Name == "Type").Should().ContainSingle().And.Subject.First();
var sqlDateValue = entityTypeColumnDefinition.GetValueFunc(sqlEntityTypePropertyDate);
sqlDateValue.Should().Be("/Person");
}

[Theory, AutoNData]
public void PersonReferencePropertyValues_ShouldBeMadeIntoStrings(
VersionChangeType versionChangeType,
Guid entityId,
Guid correlationId)
{
// arrange
var properties = new (string, ConnectorPropertyDataType)[]
{
("LastChangedBy", new EntityPropertyConnectorPropertyDataType(typeof(PersonReference))),
};
var personReferenceValue = new PersonReference("PersonName");

var personReferencePropertyData = new ConnectorPropertyData("LastChangedBy", personReferenceValue, new EntityPropertyConnectorPropertyDataType(typeof(EntityType)));
var connectorEntityData = new ConnectorEntityData(versionChangeType, StreamMode.Sync, entityId, null, null, null, null, new[] { personReferencePropertyData }, Array.Empty<IEntityCode>(), Array.Empty<EntityEdge>(), Array.Empty<EntityEdge>());
var sqlEntityTypePropertyDate = new SqlConnectorEntityData(connectorEntityData, correlationId, timestamp: DateTimeOffset.Now);

// act
var syncColumnDefinitions = MainTableDefinition.GetColumnDefinitions(StreamMode.Sync, properties);

// assert
var personReferenceColumnDefinition = syncColumnDefinitions.Where(column => column.Name == "LastChangedBy").Should().ContainSingle().And.Subject.First();
var sqlDataValue = personReferenceColumnDefinition.GetValueFunc(sqlEntityTypePropertyDate);
sqlDataValue.Should().Be("PersonName");
}

[Theory, AutoNData]
public void EntityReferencePropertyValues_ShouldBeMadeIntoStrings(
VersionChangeType versionChangeType,
Guid entityId,
Guid correlationId)
{
// arrange
var properties = new (string, ConnectorPropertyDataType)[]
{
("LastChangedBy", new EntityPropertyConnectorPropertyDataType(typeof(EntityReference))),
};
var entityCode = new EntityCode(EntityType.Person, CodeOrigin.CluedIn, "PersonName");
var entityReferenceValue = new EntityReference(entityCode);

var entityReferencePropertyData = new ConnectorPropertyData("LastChangedBy", entityReferenceValue, new EntityPropertyConnectorPropertyDataType(typeof(EntityType)));
var connectorEntityData = new ConnectorEntityData(versionChangeType, StreamMode.Sync, entityId, null, null, null, null, new[] { entityReferencePropertyData }, Array.Empty<IEntityCode>(), Array.Empty<EntityEdge>(), Array.Empty<EntityEdge>());
var sqlEntityTypePropertyDate = new SqlConnectorEntityData(connectorEntityData, correlationId, timestamp: DateTimeOffset.Now);

// act
var syncColumnDefinitions = MainTableDefinition.GetColumnDefinitions(StreamMode.Sync, properties);

// assert
var entityReferenceColumnDefinition = syncColumnDefinitions.Where(column => column.Name == "LastChangedBy").Should().ContainSingle().And.Subject.First();
var sqlDataValue = entityReferenceColumnDefinition.GetValueFunc(sqlEntityTypePropertyDate);
sqlDataValue.Should().Be("/Person#CluedIn:PersonName");
}
}
}

0 comments on commit 4466a0d

Please sign in to comment.