Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Make Oracle integration tests run and fix Oracle single statement execution code#2126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,33 +16,85 @@
//
#endregion

usingSystem;
usingSystem.Collections.Generic;

usingJetBrains.Annotations;

namespaceFluentMigrator.Runner.Generators.Oracle
{
publicclassOracleQuoter:OracleQuoterQuotedIdentifier
{
publicoverridestringQuote(stringname)
{
returnUnQuote(name);
}

publicoverridestringQuoteConstraintName(stringconstraintName,stringschemaName=null)
/// <summary>
/// https://docs.oracle.com/cd/A97630_01/appdev.920/a42525/apb.htm
/// </summary>
privatestaticreadonlyHashSet<string>_keywords=newHashSet<string>(
new[]
{
"access","else","modify","start","add","exclusive","noaudit","select",
"all","exists","nocompress","session","alter","file","not","set",
"and","float","notfound","share","any","for","nowait","size",
"arraylen","from","null","smallint","as","grant","number","sqlbuf",
"asc","group","of","successful","audit","having","offline","synonym",
"between","identified","on","sysdate","by","immediate","online","table",
"char","in","option","then","check","increment","or","to",
"cluster","index","order","trigger","column","initial","pctfree","uid",
"comment","insert","prior","union","compress","integer","privileges","unique",
"connect","intersect","public","update","create","into","raw","user",
"current","is","rename","validate","date","level","resource","values",
"decimal","like","revoke","varchar","default","lock","row","varchar2",
"delete","long","rowid","view","desc","maxextents","rowlabel","whenever",
"distinct","minus","rownum","where","drop","mode","rows","with"
},
StringComparer.OrdinalIgnoreCase);

[ContractAnnotation("name:null => false")]
protectedoverrideboolShouldQuote(stringname)
{
returnbase.QuoteConstraintName(UnQuote(constraintName),UnQuote(schemaName));
if(string.IsNullOrEmpty(name))
{
returnfalse;
}

if(_keywords.Contains(name))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why not just always quote identifiers?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Because it would be too breaking. Quoted identifiers in Oracle become case sensitive 🙃
Needless to say it breaks everything

jzabroski reacted with thumbs up emoji
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Got it. I would put that in a separate PR though with a separate issue, right? Because currently we're already in case sensitive behavior, which I can see as non-desirable. We would need to list that in the release notes as a breaking change to move to case sensitive by default. We may also want a way to easily allow forcing case sensitive identifiers to maintain backward compatibility, right?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why not, but without it the integration tests on Oracle won't work, or I ignore them for now ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ok, can you create a separate Issue and I will put it in the 7.2 milestone. I think GitHub will just generate release notes correctly.

PhenX reacted with thumbs up emoji
Copy link
CollaboratorAuthor

@PhenXPhenXAug 26, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

{
returntrue;
}

// Otherwise, quote only when it's not a valid Oracle identifier
varfirst=name[0];
if(!IsLetter(first))
{
returntrue;
}

varlen=name.Length;
for(vari=1;i<len;i++)
{
varc=name[i];
if(!IsIdentifierChar(c))
{
returntrue;
}
}

returnfalse;
}

publicoverridestringQuoteIndexName(stringindexName,stringschemaName)
privatestaticboolIsLetter(charc)
{
returnbase.QuoteIndexName(UnQuote(indexName),UnQuote(schemaName));
returncis>='A' and<='Z' or>='a' and<='z';
}

publicoverridestringQuoteTableName(stringtableName,stringschemaName=null)
privatestaticboolIsIdentifierChar(charc)
{
returnbase.QuoteTableName(UnQuote(tableName),UnQuote(schemaName));
returnIsLetter(c)||IsDigit(c)||c=='_'||c=='$'||c=='#';
}

publicoverridestringQuoteSequenceName(stringsequenceName,stringschemaName)
privatestaticboolIsDigit(charc)
{
returnbase.QuoteTableName(UnQuote(sequenceName),UnQuote(schemaName));
returncis>='0' and<='9';
}
}
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,6 +58,6 @@ public Oracle12CManagedProcessor(

/// <inheritdoc />
public override IList<string> DatabaseTypeAliases { get; } = new List<string>()
{ "Oracle-12c-Manager", "Oracle 12c Managed", "Oracle Managed", "Oracle" };
{ "Oracle-12c-Managed", "Oracle 12c Managed", "Oracle Managed", "Oracle" };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

😰

}
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,10 +17,10 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;

using FluentMigrator.Expressions;
using FluentMigrator.Runner.BatchParser;
using FluentMigrator.Runner.Generators;
using FluentMigrator.Runner.Generators.Oracle;
using FluentMigrator.Runner.Helpers;
Expand DownExpand Up@@ -195,15 +195,41 @@ public override bool SequenceExists(string schemaName, string sequenceName)
public override bool DefaultValueExists(string schemaName, string tableName, string columnName,
object defaultValue)
{
return false;
if (tableName == null)
{
throw new ArgumentNullException(nameof(tableName));
}

if (columnName == null)
{
throw new ArgumentNullException(nameof(columnName));
}

if (columnName.Length == 0 || tableName.Length == 0)
{
return false;
}

if (string.IsNullOrEmpty(schemaName))
{
return Exists(
"SELECT 1 FROM USER_TAB_COLUMNS WHERE upper(TABLE_NAME) = '{0}' AND upper(COLUMN_NAME) = '{1}' AND DATA_DEFAULT IS NOT NULL",
FormatHelper.FormatSqlEscape(tableName.ToUpper()),
FormatHelper.FormatSqlEscape(columnName.ToUpper()));
}

return Exists(
"SELECT 1 FROM ALL_TAB_COLUMNS WHERE upper(OWNER) = '{0}' AND upper(TABLE_NAME) = '{1}' AND upper(COLUMN_NAME) = '{2}' AND DATA_DEFAULT IS NOT NULL",
schemaName.ToUpper(), FormatHelper.FormatSqlEscape(tableName.ToUpper()),
FormatHelper.FormatSqlEscape(columnName.ToUpper()));
}

public override void Execute(string template, params object[] args)
public override void Execute([StructuredMessageTemplate]string template, params object[] args)
{
Process(string.Format(template, args));
}

public override bool Exists(string template, params object[] args)
public override bool Exists([StructuredMessageTemplate]string template, params object[] args)
{
if (template == null)
{
Expand DownExpand Up@@ -235,7 +261,7 @@ public override DataSet ReadTableData(string schemaName, string tableName)
return Read("SELECT * FROM {0}.{1}", Quoter.QuoteSchemaName(schemaName), Quoter.QuoteTableName(tableName));
}

public override DataSet Read(string template, params object[] args)
public override DataSet Read([StructuredMessageTemplate]string template, params object[] args)
{
if (template == null)
{
Expand DownExpand Up@@ -265,6 +291,110 @@ public override void Process(PerformDBOperationExpression expression)
expression.Operation?.Invoke(Connection, Transaction);
}

/// <summary>
/// Splits a SQL script into individual statements, taking into account Oracle-specific syntax rules.
/// <remarks>We could use <see cref="SqlBatchParser"/> but it does not handle multiple lines strings.</remarks>
/// </summary>
private static List<string> SplitOracleSqlStatements(string sqlScript)
Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I just noticed (again) the existence of SemicolonSearcher, SqlBatchParser etc, I should use it, what do you think ?

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I tried to use it (with the same implementation as SnowflakeBatchParser) but it seems that it matches semicolons inside "ranges", in my case : strings 🫠
I'm too afraid to change it 😨

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I am not a big fan of the batch parser. sql is a very complicated dialect

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Oh I think I understood ... My string spans multiple lines (I'm in the long string tests for Oracle)

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I am not a big fan of the batch parser. sql is a very complicated dialect

Yeah, neither am I, but it still a lot better than the Regex previously used

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The simplest regex would be if the line ends in a;\s*$

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

how to handle strings spaning multiple lines, containing a semicolon ? I'm sure the batch parser could be solid, but it needs to handle "ranges" spanning multiple lines, which it does not ATM.

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I finally gave up on using the SqlBatchParser, its handling of ranges line-by-line seems broken

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

my regex hack makes it so long as the last non whitespace character on a line is a; , it will treat it as a separator.

{
var statements = new List<string>();
var currentStatement = new StringBuilder();
var inString = false;
var inIdentifier = false;
var inSingleLineComment = false;
var inMultiLineComment = false;
var prevChar = '\0';

foreach (var c in sqlScript)
{
if (inSingleLineComment)
{
currentStatement.Append(c);
if (c == '\n')
{
inSingleLineComment = false;
}
continue;
}

if (inMultiLineComment)
{
currentStatement.Append(c);
if (prevChar == '*' && c == '/')
{
inMultiLineComment = false;
}
prevChar = c;
continue;
}

if (inString)
{
currentStatement.Append(c);
if (c == '\'' && prevChar != '\\')
{
inString = false;
}
prevChar = c;
continue;
}

if (inIdentifier)
{
currentStatement.Append(c);
if (c == '"')
{
inIdentifier = false;
}
prevChar = c;
continue;
}

switch (c)
{
// Check for comment start
case '-' when prevChar == '-':
inSingleLineComment = true;
currentStatement.Append(c);
continue;
case '*' when prevChar == '/':
inMultiLineComment = true;
currentStatement.Append(c);
continue;
// Check for string start
case '\'':
inString = true;
currentStatement.Append(c);
prevChar = c;
continue;
// Check for inIdentifier start
case '"':
inIdentifier = true;
currentStatement.Append(c);
prevChar = c;
continue;
// Check for statement terminator
case ';':
statements.Add(currentStatement.ToString().TrimEnd(';').Trim());
currentStatement.Clear();
prevChar = '\0';
continue;
default:
currentStatement.Append(c);
prevChar = c;
break;
}
}

// Add any remaining content
if (currentStatement.Length > 0)
{
statements.Add(currentStatement.ToString());
}

return statements;
}

protected override void Process(string sql)
{
Logger.LogSql(sql);
Expand All@@ -276,9 +406,7 @@ protected override void Process(string sql)

EnsureConnectionIsOpen();

var batches = Regex.Split(sql, @"^\s*;\s*$", RegexOptions.Multiline)
.Select(x => x.Trim())
.Where(x => !string.IsNullOrEmpty(x));
var batches = SplitOracleSqlStatements(sql);

foreach (var batch in batches)
{
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,5 +29,5 @@ public class OracleContainer : ContainerBase
protected override int Port => 1521;

/// <inheritdoc />
protected override DockerContainer Build() => new OracleBuilder().WithReuse(true).Build();
protected override DockerContainer Build() => new OracleBuilder().WithUsername("TestSchema").WithReuse(true).Build();
}
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,14 +80,16 @@ protected void ExecuteWithProcessor(
r => r
.AddFirebird()
.AddMySql4()
.AddOracle12CManaged()
.AddPostgres()
.AddSnowflake()
.AddSQLite()
.AddSqlServer2005()
.AddSqlServer2008()
.AddSqlServer2012()
.AddSqlServer2014()
.AddSqlServer2016())
.AddSqlServer2016()
)
.AddScoped<IProcessorAccessor>(
sp =>
{
Expand Down
22 changes: 17 additions & 5 deletionstest/FluentMigrator.Tests/Integration/MigrationRunnerTests.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,6 +33,7 @@
using FluentMigrator.Runner.Processors;
using FluentMigrator.Runner.Processors.Firebird;
using FluentMigrator.Runner.Processors.MySql;
using FluentMigrator.Runner.Processors.Oracle;
using FluentMigrator.Runner.Processors.Postgres;
using FluentMigrator.Runner.Processors.Snowflake;
using FluentMigrator.Runner.Processors.SQLite;
Expand DownExpand Up@@ -1150,7 +1151,8 @@ public void CanAlterTableWithSchema(Type processorType, Func<IntegrationTestOpti
[Test]
[TestCaseSource(typeof(ProcessorTestCaseSourceExcept<
SQLiteProcessor,
FirebirdProcessor
FirebirdProcessor,
OracleProcessorBase /* Oracle does not support schemas in the same way as other DBMSs */
>))]
public void CanAlterTablesSchema(Type processorType, Func<IntegrationTestOptions.DatabaseServerOptions> serverOptions)
{
Expand DownExpand Up@@ -2025,12 +2027,14 @@ internal class TestAlterColumnWithSchema: Migration
{
public override void Up()
{
Alter.Column("Name2").OnTable("TestTable2").InSchema("TestSchema").AsAnsiString(100).Nullable();
IfDatabase(processorId => !processorId.Contains(ProcessorIdConstants.Oracle))
.Alter.Column("Name2").OnTable("TestTable2").InSchema("TestSchema").AsAnsiString(100).Nullable();
}

public override void Down()
{
Alter.Column("Name2").OnTable("TestTable2").InSchema("TestSchema").AsString(10).Nullable();
IfDatabase(processorId => !processorId.Contains(ProcessorIdConstants.Oracle))
.Alter.Column("Name2").OnTable("TestTable2").InSchema("TestSchema").AsString(10).Nullable();
}
}

Expand DownExpand Up@@ -2262,12 +2266,20 @@ internal class TestExecuteSql : Migration
{
public override void Up()
{
Execute.Sql("select 1");
IfDatabase(processorId => !processorId.Contains(ProcessorIdConstants.Oracle))
.Execute.Sql("select 1");

IfDatabase(processorId => processorId.Contains(ProcessorIdConstants.Oracle))
.Execute.Sql("select 1 from dual");
}

public override void Down()
{
Execute.Sql("select 2");
IfDatabase(processorId => !processorId.Contains(ProcessorIdConstants.Oracle))
.Execute.Sql("select 1");

IfDatabase(processorId => processorId.Contains(ProcessorIdConstants.Oracle))
.Execute.Sql("select 1 from dual");
}
}
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp