Skip to content

Commit d0d6c71

Browse files
authored
Merge pull request #154 from SaiSDET/snowflake-adapter
Added an IDbAdapter for Snowflake
2 parents 1f5d920 + e471dcb commit d0d6c71

11 files changed

Lines changed: 471 additions & 4 deletions

Respawn.DatabaseTests/Respawn.DatabaseTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
1616
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
1717
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.7.0" />
18+
<PackageReference Include="Snowflake.Data" Version="4.3.0" />
1819
</ItemGroup>
1920
<ItemGroup>
2021
<ProjectReference Include="..\Respawn\Respawn.csproj" />
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
using Respawn.Graph;
2+
using Shouldly;
3+
using Snowflake.Data.Client;
4+
using System.Threading.Tasks;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
8+
namespace Respawn.DatabaseTests
9+
{
10+
public class SnowflakeTests : IAsyncLifetime
11+
{
12+
private readonly ITestOutputHelper _output;
13+
private SnowflakeDbConnection _connection;
14+
15+
public SnowflakeTests(ITestOutputHelper output) => _output = output;
16+
17+
public Task DisposeAsync()
18+
{
19+
_connection?.Close();
20+
_connection?.Dispose();
21+
_connection = null;
22+
return Task.FromResult(0);
23+
}
24+
25+
public async Task InitializeAsync()
26+
{
27+
var rootconnString = "account=YOUR_ACCOUNT;user=YOUR_USER;password=YOUR_PASSWORD;";
28+
29+
await using (var connection = new SnowflakeDbConnection(rootconnString))
30+
{
31+
await connection.OpenAsync();
32+
33+
var dropDbCommand = connection.CreateCommand();
34+
dropDbCommand.CommandText = "DROP DATABASE IF EXISTS MYTESTDB";
35+
await dropDbCommand.ExecuteNonQueryAsync();
36+
37+
var createDbCommand = connection.CreateCommand();
38+
createDbCommand.CommandText = "CREATE DATABASE MYTESTDB";
39+
await createDbCommand.ExecuteNonQueryAsync();
40+
41+
var createSchemaCommand = connection.CreateCommand();
42+
createSchemaCommand.CommandText = "CREATE SCHEMA MYTESTSCHEMA";
43+
await createSchemaCommand.ExecuteNonQueryAsync();
44+
}
45+
46+
_connection = new SnowflakeDbConnection(rootconnString + "db=MYTESTDB;schema=MYTESTSCHEMA;");
47+
await _connection.OpenAsync();
48+
}
49+
50+
private async Task ExecuteNonQueryAsync(string commandText)
51+
{
52+
using (var command = _connection.CreateCommand())
53+
{
54+
command.CommandText = commandText;
55+
await command.ExecuteNonQueryAsync();
56+
}
57+
}
58+
59+
[SkipOnCI]
60+
public async Task ShouldDeleteData()
61+
{
62+
await ExecuteNonQueryAsync("CREATE OR REPLACE TABLE foo (value INT)");
63+
64+
for (int i = 0; i < 10; i++)
65+
{
66+
await InsertDataAsync("foo", i);
67+
}
68+
69+
long countBeforeReset = await CountRowsAsync("foo");
70+
countBeforeReset.ShouldBe(10L);
71+
72+
var checkpoint = await Respawner.CreateAsync(_connection, new RespawnerOptions
73+
{
74+
DbAdapter = DbAdapter.Snowflake
75+
});
76+
77+
await checkpoint.ResetAsync(_connection);
78+
79+
long countAfterReset = await CountRowsAsync("foo");
80+
countAfterReset.ShouldBe(0L);
81+
}
82+
83+
[SkipOnCI]
84+
public async Task ShouldIgnoreTables()
85+
{
86+
await CreateTableAsync("Foo");
87+
await CreateTableAsync("Bar");
88+
89+
for (int i = 0; i < 10; i++)
90+
{
91+
await InsertDataAsync("Foo", i);
92+
await InsertDataAsync("Bar", i);
93+
}
94+
95+
var checkpoint = await Respawner.CreateAsync(_connection, new RespawnerOptions
96+
{
97+
DbAdapter = DbAdapter.Snowflake,
98+
TablesToIgnore = new Table[] { "Foo" },
99+
SchemasToInclude = new[] { "MYTESTSCHEMA" }
100+
});
101+
102+
await checkpoint.ResetAsync(_connection);
103+
104+
long countFoo = await CountRowsAsync("Foo");
105+
countFoo.ShouldBe(10);
106+
107+
long countBar = await CountRowsAsync("Bar");
108+
countBar.ShouldBe(0L);
109+
}
110+
111+
[SkipOnCI]
112+
public async Task ShouldIgnoreTablesIfSchemaSpecified()
113+
{
114+
await ExecuteNonQueryAsync("DROP SCHEMA IF EXISTS eggs");
115+
await ExecuteNonQueryAsync("CREATE SCHEMA eggs");
116+
117+
await ExecuteNonQueryAsync("CREATE TABLE eggs.foo (Value INT)");
118+
await ExecuteNonQueryAsync("CREATE TABLE eggs.bar (Value INT)");
119+
120+
for (int i = 0; i < 10; i++)
121+
{
122+
await InsertDataAsync("eggs.foo", i);
123+
await InsertDataAsync("eggs.bar", i);
124+
}
125+
126+
var checkpoint = await Respawner.CreateAsync(_connection, new RespawnerOptions
127+
{
128+
DbAdapter = DbAdapter.Snowflake,
129+
TablesToIgnore = new Table[] { new Table("eggs", "foo") }
130+
});
131+
132+
await checkpoint.ResetAsync(_connection);
133+
134+
long countFoo = await CountRowsAsync("eggs.foo");
135+
countFoo.ShouldBe(10L);
136+
137+
long countBar = await CountRowsAsync("eggs.bar");
138+
countBar.ShouldBe(0L);
139+
}
140+
141+
[SkipOnCI]
142+
public async Task ShouldIncludeTables()
143+
{
144+
await CreateTableAsync("Foo");
145+
await CreateTableAsync("Bar");
146+
147+
for (int i = 0; i < 10; i++)
148+
{
149+
await InsertDataAsync("foo", i);
150+
await InsertDataAsync("bar", i);
151+
}
152+
153+
var checkpoint = await Respawner.CreateAsync(_connection, new RespawnerOptions
154+
{
155+
DbAdapter = DbAdapter.Snowflake,
156+
TablesToInclude = new Table[] { "foo" }
157+
});
158+
await checkpoint.ResetAsync(_connection);
159+
160+
long countFoo = await CountRowsAsync("Foo");
161+
countFoo.ShouldBe(0);
162+
163+
long countBar = await CountRowsAsync("Bar");
164+
countBar.ShouldBe(10L);
165+
}
166+
167+
[SkipOnCI]
168+
public async Task ShouldIncludeTablesIfSchemaSpecified()
169+
{
170+
await ExecuteNonQueryAsync("DROP SCHEMA IF EXISTS eggs");
171+
await ExecuteNonQueryAsync("CREATE SCHEMA eggs");
172+
173+
await ExecuteNonQueryAsync("CREATE TABLE eggs.foo (Value INT)");
174+
await ExecuteNonQueryAsync("CREATE TABLE eggs.bar (Value INT)");
175+
176+
for (int i = 0; i < 10; i++)
177+
{
178+
await InsertDataAsync("eggs.foo", i);
179+
await InsertDataAsync("eggs.bar", i);
180+
}
181+
182+
var checkpoint = await Respawner.CreateAsync(_connection, new RespawnerOptions
183+
{
184+
DbAdapter = DbAdapter.Snowflake,
185+
TablesToInclude = new Table[] { new Table("eggs", "foo") }
186+
});
187+
await checkpoint.ResetAsync(_connection);
188+
189+
long countFoo = await CountRowsAsync("Foo");
190+
countFoo.ShouldBe(0);
191+
192+
long countBar = await CountRowsAsync("Bar");
193+
countBar.ShouldBe(10L);
194+
}
195+
196+
[SkipOnCI]
197+
public async Task ShouldExcludeSchemas()
198+
{
199+
await ExecuteNonQueryAsync("CREATE SCHEMA a");
200+
await ExecuteNonQueryAsync("CREATE SCHEMA b");
201+
202+
await ExecuteNonQueryAsync("CREATE TABLE a.foo (Value INT)");
203+
await ExecuteNonQueryAsync("CREATE TABLE b.bar (Value INT)");
204+
205+
for (int i = 0; i < 10; i++)
206+
{
207+
await InsertDataAsync("a.foo", i);
208+
await InsertDataAsync("b.bar", i);
209+
}
210+
211+
var checkpoint = await Respawner.CreateAsync(_connection, new RespawnerOptions
212+
{
213+
DbAdapter = DbAdapter.Snowflake,
214+
SchemasToExclude = new[] { "a" }
215+
});
216+
217+
await checkpoint.ResetAsync(_connection);
218+
219+
long countFoo = await CountRowsAsync("a.Foo");
220+
countFoo.ShouldBe(10L);
221+
222+
long countBar = await CountRowsAsync("Bar");
223+
countBar.ShouldBe(0);
224+
}
225+
226+
[SkipOnCI]
227+
public async Task ShouldIncludeSchemas()
228+
{
229+
await ExecuteNonQueryAsync("CREATE SCHEMA a");
230+
await ExecuteNonQueryAsync("CREATE SCHEMA b");
231+
232+
await ExecuteNonQueryAsync("CREATE TABLE a.foo (Value INT)");
233+
await ExecuteNonQueryAsync("CREATE TABLE b.bar (Value INT)");
234+
235+
for (int i = 0; i < 10; i++)
236+
{
237+
await InsertDataAsync("a.foo", i);
238+
await InsertDataAsync("b.bar", i);
239+
}
240+
241+
var checkpoint = await Respawner.CreateAsync(_connection, new RespawnerOptions
242+
{
243+
DbAdapter = DbAdapter.Snowflake,
244+
SchemasToInclude = new[] { "b" }
245+
});
246+
await checkpoint.ResetAsync(_connection);
247+
248+
long countFoo = await CountRowsAsync("a.Foo");
249+
countFoo.ShouldBe(10L);
250+
251+
long countBar = await CountRowsAsync("Bar");
252+
countBar.ShouldBe(0);
253+
}
254+
255+
private async Task InsertDataAsync(string tableName, int value)
256+
{
257+
using (var command = _connection.CreateCommand())
258+
{
259+
command.CommandText = $"INSERT INTO {tableName} (Value) VALUES ({value})";
260+
await command.ExecuteNonQueryAsync();
261+
}
262+
}
263+
264+
private async Task<long> CountRowsAsync(string tableName)
265+
{
266+
using (var command = _connection.CreateCommand())
267+
{
268+
command.CommandText = $"SELECT COUNT(1) FROM {tableName}";
269+
return (long)await command.ExecuteScalarAsync();
270+
}
271+
}
272+
273+
private async Task CreateTableAsync(string tableName)
274+
{
275+
using (var command = _connection.CreateCommand())
276+
{
277+
command.CommandText = $"CREATE TABLE {tableName} (value INT)";
278+
await command.ExecuteNonQueryAsync();
279+
}
280+
}
281+
}
282+
}

Respawn/DbAdapter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ public static class DbAdapter
88
public static readonly IDbAdapter Oracle = new OracleDbAdapter();
99
public static readonly IDbAdapter Informix = new InformixDbAdapter();
1010
public static readonly IDbAdapter DB2 = new DB2DbAdapter();
11+
public static readonly IDbAdapter Snowflake = new SnowflakeDbAdapter();
1112
}
1213
}

Respawn/IDbAdapter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ Task<bool> CheckSupportsTemporalTables(DbConnection connection)
1818
{
1919
return Task.FromResult(false);
2020
}
21+
bool RequiresStatementsToBeExecutedIndividually();
2122
}
2223
}

Respawn/InformixDbAdapter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,5 +209,7 @@ public Task<bool> CheckSupportsTemporalTables(DbConnection connection)
209209
{
210210
return Task.FromResult(false);
211211
}
212+
213+
public bool RequiresStatementsToBeExecutedIndividually() => false;
212214
}
213215
}

Respawn/MySqlAdapter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,5 +213,7 @@ public Task<bool> CheckSupportsTemporalTables(DbConnection connection)
213213
{
214214
return Task.FromResult(false);
215215
}
216+
217+
public bool RequiresStatementsToBeExecutedIndividually() => false;
216218
}
217219
}

Respawn/OracleDbAdapter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,5 +204,7 @@ public Task<bool> CheckSupportsTemporalTables(DbConnection connection)
204204
{
205205
return Task.FromResult(false);
206206
}
207+
208+
public bool RequiresStatementsToBeExecutedIndividually() => false;
207209
}
208210
}

Respawn/PostgresDbAdapter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,5 +254,7 @@ public Task<bool> CheckSupportsTemporalTables(DbConnection connection)
254254
{
255255
return Task.FromResult(false);
256256
}
257+
258+
public bool RequiresStatementsToBeExecutedIndividually() => false;
257259
}
258260
}

0 commit comments

Comments
 (0)