Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import TableProSQLGrammar
///
/// The batch is split into statements the way its engine lexes it, by the grammar the Mac reads
/// too, and under every reading the engine could be using: the run splits by one reading, and a
/// `DELETE` hidden behind a quote that only another reading closes still reaches the server. On an
/// `DELETE` hidden behind a quote that only another reading closes still reaches the server. Each
/// MySQL server version that skips a different set of `/*!` comments is one more reading. On an
/// engine that needs no `;`, every statement that begins inside one is read as well, because SQL
/// Server runs `SELECT 1` followed by `DROP TABLE t` as two statements.
public enum SQLWriteClassifier {
Expand Down Expand Up @@ -43,10 +44,13 @@ public enum SQLWriteClassifier {
return verdict.tier != .read
}
let readings = SQLLexicalReadings.resolve(databaseTypeId: databaseType.rawValue, declared: nil, session: nil)
return readings.distinct(for: sql).contains { grammar in
SQLStatementScanner.executableStatements(in: sql, grammar: grammar).contains { statement in
SQLUnterminatedStatements.runnable(in: statement.sql, grammar: grammar).contains { runnable in
statementWrites(runnable, grammar: grammar)
guard let lexings = readings.lexings(of: sql) else { return true }
var seen: Set<SQLLexing> = []
return lexings.contains { lexing in
SQLStatementScanner.executableStatements(in: lexing.text, grammar: lexing.grammar).contains { statement in
SQLUnterminatedStatements.runnable(in: statement.sql, grammar: lexing.grammar).contains { runnable in
seen.insert(SQLLexing(grammar: lexing.grammar, text: runnable)).inserted
&& statementWrites(runnable, grammar: lexing.grammar)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,15 @@ public enum SQLCatalogEffectClassifier {
}
}

// A `DROP` that only a server skipping a `/*!` comment runs still changes the catalog.
public static func effect(of sql: String, grammar: SQLLexicalGrammar) -> SQLCatalogEffect {
guard let texts = SQLExecutableCommentReadings.texts(of: sql, grammar: grammar) else { return .opaque }
return texts.reduce(SQLCatalogEffect.none) { partial, text in
partial.union(textEffect(of: text, grammar: grammar))
}
}

private static func textEffect(of sql: String, grammar: SQLLexicalGrammar) -> SQLCatalogEffect {
let trimmed = StatementBlank.trimming(SQLLeadingComments.stripping(sql))
guard !trimmed.isEmpty else { return .none }
if SQLProceduralBlock.runs(trimmed, grammar: grammar) {
Expand Down Expand Up @@ -201,9 +209,9 @@ public enum SQLCatalogEffectClassifier {
]
}

// A MySQL executable comment (/*! and /*M!) is code, so it is never skipped.
// A MySQL executable comment (/*! and /*M!) is code, and so is TiDB's /*T!, so it is never skipped.
public enum SQLLeadingComments {
public static let conditionalCommentOpeners: [String] = ["/*!", "/*M!"]
public static let conditionalCommentOpeners: [String] = ["/*!", "/*M!", "/*T!"]

public static let lineCommentTerminators: Set<Character> = ["\n", "\r", "\r\n"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import Foundation
/// a string that ended where the engine ends it always does. Each blanked UTF-16 unit becomes a space and each line
/// feed stays, so an offset into the projection is an offset into the original text.
public enum SQLCodeProjection {
/// - Parameter revealingExecutableComments: whether a MySQL `/*! ... */` body is kept as code. A gate reveals it
/// whatever the grammar says, because the only cost of reading an ignored comment as code is a stricter tier.
/// - Parameter revealingExecutableComments: whether a MySQL `/*! ... */` body is kept as code, as a server that runs
/// it reads it. What a server that skips it runs is ``SQLExecutableCommentReadings``'s question.
public static func code(
of text: String,
grammar: SQLLexicalGrammar,
Expand Down
Loading
Loading