@@ -164,6 +164,165 @@ public void BuildQueryString_MigrationCommandListBuilder_WritesNothingForEmptyLi
164164 mockBuilder . Verify ( b => b . Append ( It . IsAny < string > ( ) ) , Times . Never ) ;
165165 mockBuilder . Verify ( b => b . EndCommand ( It . IsAny < bool > ( ) ) , Times . Never ) ;
166166 }
167+ #region ReplaceSelectWithPerform
168+
169+ [ Fact ]
170+ public void ReplaceSelectWithPerform_ReplacesLeadingSelect ( )
171+ {
172+ string input = "SELECT create_hypertable('public.\" Events\" ', 'CapturedAt');" ;
173+ string result = SqlBuilderHelper . ReplaceSelectWithPerform ( input ) ;
174+ Assert . Equal ( "PERFORM create_hypertable('public.\" Events\" ', 'CapturedAt');" , result ) ;
175+ }
176+
177+ [ Fact ]
178+ public void ReplaceSelectWithPerform_PreservesLeadingWhitespace ( )
179+ {
180+ string input = " SELECT add_dimension('public.\" Events\" ', by_range('sensor_id'));" ;
181+ string result = SqlBuilderHelper . ReplaceSelectWithPerform ( input ) ;
182+ Assert . Equal ( " PERFORM add_dimension('public.\" Events\" ', by_range('sensor_id'));" , result ) ;
183+ }
184+
185+ [ Fact ]
186+ public void ReplaceSelectWithPerform_PreservesNonSelectStatements ( )
187+ {
188+ string input = "ALTER TABLE \" public\" .\" Events\" SET (timescaledb.compress = true);" ;
189+ string result = SqlBuilderHelper . ReplaceSelectWithPerform ( input ) ;
190+ Assert . Equal ( input , result ) ;
191+ }
192+
193+ [ Fact ]
194+ public void ReplaceSelectWithPerform_PreservesDoBlocks ( )
195+ {
196+ string input = "DO $$ BEGIN EXECUTE 'SELECT 1'; END $$;" ;
197+ string result = SqlBuilderHelper . ReplaceSelectWithPerform ( input ) ;
198+ Assert . Equal ( input , result ) ;
199+ }
200+
201+ [ Fact ]
202+ public void ReplaceSelectWithPerform_IsCaseInsensitive ( )
203+ {
204+ string input = "select remove_retention_policy('public.\" Events\" ', if_exists => true);" ;
205+ string result = SqlBuilderHelper . ReplaceSelectWithPerform ( input ) ;
206+ Assert . StartsWith ( "PERFORM" , result ) ;
207+ }
208+
209+ [ Fact ]
210+ public void ReplaceSelectWithPerform_HandlesMultiLineAlterJob ( )
211+ {
212+ string input = @"
213+ SELECT alter_job(job_id, schedule_interval => INTERVAL '2 days')
214+ FROM timescaledb_information.jobs
215+ WHERE proc_name = 'policy_retention' AND hypertable_schema = 'public' AND hypertable_name = 'TestTable';" . Trim ( ) ;
216+
217+ string result = SqlBuilderHelper . ReplaceSelectWithPerform ( input ) ;
218+
219+ Assert . StartsWith ( "PERFORM alter_job" , result ) ;
220+ Assert . Contains ( "FROM timescaledb_information.jobs" , result ) ;
221+ Assert . DoesNotContain ( "SELECT" , result ) ;
222+ }
223+
224+ [ Fact ]
225+ public void ReplaceSelectWithPerformMultiLine_ReplacesAllSelectStatements ( )
226+ {
227+ string input = """
228+ SELECT create_hypertable('public."Events"', 'Time');
229+ SELECT add_dimension('public."Events"', by_range('sensor_id', 100));
230+ SELECT alter_job(job_id, schedule_interval => INTERVAL '1 day')
231+ FROM timescaledb_information.jobs
232+ WHERE proc_name = 'policy_retention';
233+ """ ;
234+
235+ string result = SqlBuilderHelper . ReplaceSelectWithPerformMultiLine ( input ) ;
236+
237+ Assert . DoesNotContain ( "SELECT create_hypertable" , result ) ;
238+ Assert . DoesNotContain ( "SELECT add_dimension" , result ) ;
239+ Assert . DoesNotContain ( "SELECT alter_job" , result ) ;
240+ Assert . Contains ( "PERFORM create_hypertable" , result ) ;
241+ Assert . Contains ( "PERFORM add_dimension" , result ) ;
242+ Assert . Contains ( "PERFORM alter_job" , result ) ;
243+ Assert . Contains ( "FROM timescaledb_information.jobs" , result ) ;
244+ }
245+
246+ [ Fact ]
247+ public void ReplaceSelectWithPerformMultiLine_PreservesDoBlocks ( )
248+ {
249+ string input = """
250+ SELECT create_hypertable('public."Events"', 'Time');
251+ DO $$
252+ BEGIN
253+ EXECUTE 'SELECT enable_chunk_skipping(...)';
254+ END $$;
255+ """ ;
256+
257+ string result = SqlBuilderHelper . ReplaceSelectWithPerformMultiLine ( input ) ;
258+
259+ Assert . Contains ( "PERFORM create_hypertable" , result ) ;
260+ Assert . Contains ( "DO $$" , result ) ;
261+ Assert . Contains ( "EXECUTE 'SELECT enable_chunk_skipping(...)'" , result ) ;
262+ }
263+
264+ #endregion
265+
266+ #region BuildQueryString_MigrationCommandListBuilder_UsePerform
267+
268+ [ Fact ]
269+ public void BuildQueryString_MigrationCommandListBuilder_UsePerform_ReplacesSelectWithPerform ( )
270+ {
271+ // Arrange
272+ List < string > statements = [ "SELECT create_hypertable('public.\" Events\" ', 'Time');" ] ;
273+ MigrationsSqlGeneratorDependencies dependencies = new (
274+ Mock . Of < IRelationalCommandBuilderFactory > ( ) ,
275+ Mock . Of < IUpdateSqlGenerator > ( ) ,
276+ Mock . Of < ISqlGenerationHelper > ( ) ,
277+ Mock . Of < IRelationalTypeMappingSource > ( ) ,
278+ Mock . Of < ICurrentDbContext > ( ) ,
279+ Mock . Of < IModificationCommandFactory > ( ) ,
280+ Mock . Of < ILoggingOptions > ( ) ,
281+ Mock . Of < IRelationalCommandDiagnosticsLogger > ( ) ,
282+ Mock . Of < IDiagnosticsLogger < DbLoggerCategory . Migrations > > ( )
283+ ) ;
284+
285+ Mock < MigrationCommandListBuilder > mockBuilder = new ( dependencies ) ;
286+ mockBuilder . Setup ( b => b . Append ( It . IsAny < string > ( ) ) ) . Returns ( mockBuilder . Object ) ;
287+ mockBuilder . Setup ( b => b . EndCommand ( It . IsAny < bool > ( ) ) ) . Returns ( mockBuilder . Object ) ;
288+
289+ // Act
290+ SqlBuilderHelper . BuildQueryString ( statements , mockBuilder . Object , usePerform : true ) ;
291+
292+ // Assert
293+ mockBuilder . Verify ( b => b . Append ( It . Is < string > ( s => s . StartsWith ( "PERFORM" ) ) ) , Times . Once ) ;
294+ mockBuilder . Verify ( b => b . Append ( It . Is < string > ( s => s . StartsWith ( "SELECT" ) ) ) , Times . Never ) ;
295+ }
296+
297+ [ Fact ]
298+ public void BuildQueryString_MigrationCommandListBuilder_UsePerform_False_PreservesSelect ( )
299+ {
300+ // Arrange
301+ List < string > statements = [ "SELECT create_hypertable('public.\" Events\" ', 'Time');" ] ;
302+ MigrationsSqlGeneratorDependencies dependencies = new (
303+ Mock . Of < IRelationalCommandBuilderFactory > ( ) ,
304+ Mock . Of < IUpdateSqlGenerator > ( ) ,
305+ Mock . Of < ISqlGenerationHelper > ( ) ,
306+ Mock . Of < IRelationalTypeMappingSource > ( ) ,
307+ Mock . Of < ICurrentDbContext > ( ) ,
308+ Mock . Of < IModificationCommandFactory > ( ) ,
309+ Mock . Of < ILoggingOptions > ( ) ,
310+ Mock . Of < IRelationalCommandDiagnosticsLogger > ( ) ,
311+ Mock . Of < IDiagnosticsLogger < DbLoggerCategory . Migrations > > ( )
312+ ) ;
313+
314+ Mock < MigrationCommandListBuilder > mockBuilder = new ( dependencies ) ;
315+ mockBuilder . Setup ( b => b . Append ( It . IsAny < string > ( ) ) ) . Returns ( mockBuilder . Object ) ;
316+ mockBuilder . Setup ( b => b . EndCommand ( It . IsAny < bool > ( ) ) ) . Returns ( mockBuilder . Object ) ;
317+
318+ // Act
319+ SqlBuilderHelper . BuildQueryString ( statements , mockBuilder . Object , usePerform : false ) ;
320+
321+ // Assert
322+ mockBuilder . Verify ( b => b . Append ( It . Is < string > ( s => s . StartsWith ( "SELECT" ) ) ) , Times . Once ) ;
323+ }
324+
325+ #endregion
167326 }
168327#pragma warning restore EF1001 // Internal EF Core API usage.
169328}
0 commit comments