From 8e8375d64e3dca78b4b11d43c085f44866d20beb Mon Sep 17 00:00:00 2001 From: ItsDeadlyProgrammer Date: Mon, 19 Oct 2026 10:15:30 +0530 Subject: [PATCH 1/4] Escape backslashes in SQL string values for MySQL-like dialects MySQL, MariaDB, BigQuery, Spark SQL and the other dialects that escape a quote with a backslash also treat a lone backslash as an escape character. A value such as C:\temp\ was written as 'C:\temp\', where the last backslash escapes the closing quote and the statement is broken. Double the backslash in these dialects (and without a dialect, which uses the same backslash escaping). Dialects that double quotes are unchanged. --- .../transformations/sql/SqlTransformer.java | 5 +++- .../java/net/datafaker/formats/SqlTest.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java index 888f9c65d..6a4006880 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java @@ -228,7 +228,10 @@ private String handledObjectToString(int length, String strValue) { for (int k = 0; k < length; k++) { if (strValue.charAt(k) == quote) { builder.append(quoteEscape).append(quote); - }else { + } else if (strValue.charAt(k) == '\\' && quoteEscape == '\\') { + // where a backslash escapes the quote, a literal backslash has to be escaped too + builder.append("\\\\"); + } else { builder.append(strValue.charAt(k)); } } diff --git a/src/test/java/net/datafaker/formats/SqlTest.java b/src/test/java/net/datafaker/formats/SqlTest.java index 7a9f58c1d..af5e5792e 100644 --- a/src/test/java/net/datafaker/formats/SqlTest.java +++ b/src/test/java/net/datafaker/formats/SqlTest.java @@ -620,4 +620,32 @@ private static Stream quoteEscapeDialects() { of(SqlDialect.SPARKSQL, "'O\\'Brien'") ); } + + @ParameterizedTest + @MethodSource("backslashEscapeDialects") + void escapesBackslashInStringValues(SqlDialect dialect, String expectedValue) { + Schema schema = Schema.of(field("path", () -> "C:\\temp\\")); + SqlTransformer transformer = SqlTransformer.builder().dialect(dialect).build(); + + assertThat(transformer.generate(schema, 1)).endsWith("VALUES (" + expectedValue + ");"); + } + + private static Stream backslashEscapeDialects() { + return Stream.of( + of(SqlDialect.POSTGRES, "'C:\\temp\\'"), + of(SqlDialect.MSSQL, "'C:\\temp\\'"), + of(SqlDialect.MYSQL, "'C:\\\\temp\\\\'"), + of(SqlDialect.MARIADB, "'C:\\\\temp\\\\'"), + of(SqlDialect.BIGQUERY, "'C:\\\\temp\\\\'"), + of(SqlDialect.SPARKSQL, "'C:\\\\temp\\\\'") + ); + } + + @Test + void escapesBackslashBeforeQuote() { + Schema schema = Schema.of(field("text", () -> "a\\'b")); + SqlTransformer transformer = SqlTransformer.builder().dialect(SqlDialect.MYSQL).build(); + + assertThat(transformer.generate(schema, 1)).endsWith("VALUES ('a\\\\\\'b');"); + } } From 3b1c192da94c8dba219b64b5d9363644a1f713ca Mon Sep 17 00:00:00 2001 From: ItsDeadlyProgrammer Date: Tue, 20 Oct 2026 18:30:32 +0530 Subject: [PATCH 2/4] Let each SQL dialect escape its own string literals The backslash rule is now a constructor parameter of SqlDialect instead of a switch, and SqlTransformer asks the dialect to escape the value. --- .../transformations/sql/SqlDialect.java | 66 ++++++++++++++----- .../transformations/sql/SqlTransformer.java | 23 +------ 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java index a36c5cf3e..68ab686c6 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java @@ -7,6 +7,7 @@ import static net.datafaker.transformations.Transformer.LINE_SEPARATOR; import static net.datafaker.transformations.sql.Casing.DEFAULT_CASING; +import static net.datafaker.transformations.sql.SqlDialect.AuxiliaryConstants.BACKSLASH_ESCAPE; import static net.datafaker.transformations.sql.SqlDialect.AuxiliaryConstants.DEFAULT_FIRST_ROW; import static net.datafaker.transformations.sql.SqlDialect.AuxiliaryConstants.DEFAULT_OTHER_ROWS; import static net.datafaker.transformations.sql.SqlTransformer.SQLKeyWords.INSERT_ALL; @@ -19,17 +20,17 @@ public enum SqlDialect { ANSI("`"), - BIGQUERY("`", Casing.UNCHANGED), + BIGQUERY("`", Casing.UNCHANGED, BACKSLASH_ESCAPE), CALCITE("\""), - CLICKHOUSE("`"), + CLICKHOUSE("`", BACKSLASH_ESCAPE), EXASOL("\""), FIREBOLT("\""), H2("\""), - INFOBRIGHT("`"), + INFOBRIGHT("`", BACKSLASH_ESCAPE), LUCIDDB("\""), - MARIADB("`", Casing.TO_LOWER), + MARIADB("`", Casing.TO_LOWER, BACKSLASH_ESCAPE), MSSQL("[]"), - MYSQL("`", Casing.UNCHANGED), + MYSQL("`", Casing.UNCHANGED, BACKSLASH_ESCAPE), NETEZZA("\""), ORACLE("\"", Casing.TO_UPPER, (columns, values, caze) -> { @@ -48,7 +49,7 @@ public enum SqlDialect { PRESTO("\"", Casing.UNCHANGED), REDSHIFT("\"", Casing.TO_LOWER), SNOWFLAKE("\""), - SPARKSQL("`") { + SPARKSQL("`", BACKSLASH_ESCAPE) { @Override public String getCompositePrefix(SqlTransformer.Case caze) { @@ -77,30 +78,46 @@ public String getFieldPrefix(String fieldName) { private final TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow; private final TriFunction, Supplier, SqlTransformer.Case, String> batchOtherRows; private final Function lastBatchRow; + private final boolean backslashEscape; private static final String DEFAULT_BEFORE_EACH_BATCH_PREFIX = " "; - SqlDialect(String sqlQuoteIdentifier, Casing casing, TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow, + SqlDialect(String sqlQuoteIdentifier, Casing casing, boolean backslashEscape, + TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow, TriFunction, Supplier, SqlTransformer.Case, String> batchOtherRows, Function lastBatchRow) { this.sqlQuoteIdentifier = sqlQuoteIdentifier; this.unquotedCasing = casing; + this.backslashEscape = backslashEscape; this.batchFirstRow = batchFirstRow; this.batchOtherRows = batchOtherRows; this.lastBatchRow = lastBatchRow; } + SqlDialect(String sqlQuoteIdentifier, Casing casing, TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow, + TriFunction, Supplier, SqlTransformer.Case, String> batchOtherRows, Function lastBatchRow) { + this(sqlQuoteIdentifier, casing, false, batchFirstRow, batchOtherRows, lastBatchRow); + } + SqlDialect(String sqlQuoteIdentifier, TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow, TriFunction, Supplier, SqlTransformer.Case, String> batchOtherRows, Function lastBatchRow) { this(sqlQuoteIdentifier, DEFAULT_CASING, batchFirstRow, batchOtherRows, lastBatchRow); } - SqlDialect(String sqlQuoteIdentifier, Casing casing) { - this(sqlQuoteIdentifier, casing, + SqlDialect(String sqlQuoteIdentifier, Casing casing, boolean backslashEscape) { + this(sqlQuoteIdentifier, casing, backslashEscape, DEFAULT_FIRST_ROW, DEFAULT_OTHER_ROWS, s -> ""); } + SqlDialect(String sqlQuoteIdentifier, Casing casing) { + this(sqlQuoteIdentifier, casing, false); + } + + SqlDialect(String sqlQuoteIdentifier, boolean backslashEscape) { + this(sqlQuoteIdentifier, DEFAULT_CASING, backslashEscape); + } + SqlDialect(String sqlQuoteIdentifier) { this(sqlQuoteIdentifier, DEFAULT_CASING); } @@ -148,17 +165,34 @@ public String getArrayEnd() { } /** - * Whether a quote inside a string literal is escaped with a backslash ({@code 'It\'s'}). - * Other dialects double it ({@code 'It''s'}) as standard SQL requires. + * Escapes a value placed inside a string literal delimited by {@code quote}. + * Most dialects double the quote ({@code 'It''s'}) as standard SQL requires. Dialects created with + * {@code BACKSLASH_ESCAPE} put a backslash before the quote and before a literal backslash + * ({@code 'It\'s'}, {@code 'C:\\temp'}). */ - public boolean escapesQuoteWithBackslash() { - return switch (this) { - case BIGQUERY, CLICKHOUSE, INFOBRIGHT, MARIADB, MYSQL, SPARKSQL -> true; - default -> false; - }; + public String escapeStringLiteral(String value, char quote) { + return escape(value, quote, backslashEscape ? '\\' : quote); + } + + /** + * Puts {@code escape} before every {@code quote} and every {@code escape} in the value. + * When both are the same character, this doubles it. + */ + static String escape(String value, char quote, char escape) { + StringBuilder result = new StringBuilder(value.length()); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == quote || c == escape) { + result.append(escape); + } + result.append(c); + } + return result.toString(); } static class AuxiliaryConstants { + static final boolean BACKSLASH_ESCAPE = true; + static final TriFunction, Supplier, SqlTransformer.Case, String> DEFAULT_FIRST_ROW = (supplier, supplier2, caze) -> { final String insertAll = INSERT_INTO.getValue(caze) + " "; final String values = LINE_SEPARATOR + VALUES.getValue(caze) + " "; diff --git a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java index 6a4006880..160f9800a 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java @@ -32,7 +32,6 @@ public class SqlTransformer implements Transformer { private final Casing casing; private final char quote; - private final char quoteEscape; private final char openSqlIdentifier; private final char closeSqlIdentifier; private final String tableName; @@ -53,7 +52,6 @@ private SqlTransformer(String schemaName, String tableName, char quote, @Nullabl Casing casing, boolean withBatchMode, int batchSize, Case keywordCase, boolean forceSqlQuoteIdentifierUsage) { this.schemaName = schemaName; this.quote = quote; - this.quoteEscape = dialect == null || dialect.escapesQuoteWithBackslash() ? '\\' : quote; this.dialect = dialect; this.openSqlIdentifier = sqlIdentifier.charAt(0); this.closeSqlIdentifier = sqlIdentifier.length() == 1 ? sqlIdentifier.charAt(0) : sqlIdentifier.charAt(1); @@ -214,30 +212,15 @@ private String handleObject(@Nullable Object value) { + "[" + handleObjectInCollection(collection) + "]"; } else { String strValue = value.toString(); - final int length = strValue.length(); final boolean quoteRequired = !(value instanceof Number) && !(value instanceof Boolean); - String res = handledObjectToString(length, strValue); + String res = dialect == null + ? SqlDialect.escape(strValue, quote, '\\') + : dialect.escapeStringLiteral(strValue, quote); return quoteRequired ? quote + res + quote : res; } } } - private String handledObjectToString(int length, String strValue) { - StringBuilder builder = new StringBuilder(length); - - for (int k = 0; k < length; k++) { - if (strValue.charAt(k) == quote) { - builder.append(quoteEscape).append(quote); - } else if (strValue.charAt(k) == '\\' && quoteEscape == '\\') { - // where a backslash escapes the quote, a literal backslash has to be escaped too - builder.append("\\\\"); - } else { - builder.append(strValue.charAt(k)); - } - } - return builder.toString(); - } - private String handlePrimitivesInArray(Class componentType, Object value) { StringJoiner joiner = new StringJoiner(", "); if (componentType == byte.class) { From 23c567d35eebfd262e8f418f630b0a919b494c66 Mon Sep 17 00:00:00 2001 From: ItsDeadlyProgrammer Date: Sat, 24 Oct 2026 22:42:45 +0530 Subject: [PATCH 3/4] Review: escape SPARKSQL field prefix, QuoteEscape enum, keep no-dialect behaviour, SNOWFLAKE backslash --- .../transformations/sql/SqlDialect.java | 65 ++++++++++++------- .../transformations/sql/SqlTransformer.java | 16 +++-- .../java/net/datafaker/formats/SqlTest.java | 29 +++++++++ 3 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java index 68ab686c6..faa9e3c35 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java @@ -7,7 +7,7 @@ import static net.datafaker.transformations.Transformer.LINE_SEPARATOR; import static net.datafaker.transformations.sql.Casing.DEFAULT_CASING; -import static net.datafaker.transformations.sql.SqlDialect.AuxiliaryConstants.BACKSLASH_ESCAPE; +import static net.datafaker.transformations.sql.SqlDialect.QuoteEscape.BACKSLASH; import static net.datafaker.transformations.sql.SqlDialect.AuxiliaryConstants.DEFAULT_FIRST_ROW; import static net.datafaker.transformations.sql.SqlDialect.AuxiliaryConstants.DEFAULT_OTHER_ROWS; import static net.datafaker.transformations.sql.SqlTransformer.SQLKeyWords.INSERT_ALL; @@ -20,17 +20,17 @@ public enum SqlDialect { ANSI("`"), - BIGQUERY("`", Casing.UNCHANGED, BACKSLASH_ESCAPE), + BIGQUERY("`", Casing.UNCHANGED, BACKSLASH), CALCITE("\""), - CLICKHOUSE("`", BACKSLASH_ESCAPE), + CLICKHOUSE("`", BACKSLASH), EXASOL("\""), FIREBOLT("\""), H2("\""), - INFOBRIGHT("`", BACKSLASH_ESCAPE), + INFOBRIGHT("`", BACKSLASH), LUCIDDB("\""), - MARIADB("`", Casing.TO_LOWER, BACKSLASH_ESCAPE), + MARIADB("`", Casing.TO_LOWER, BACKSLASH), MSSQL("[]"), - MYSQL("`", Casing.UNCHANGED, BACKSLASH_ESCAPE), + MYSQL("`", Casing.UNCHANGED, BACKSLASH), NETEZZA("\""), ORACLE("\"", Casing.TO_UPPER, (columns, values, caze) -> { @@ -48,8 +48,8 @@ public enum SqlDialect { POSTGRES("\""), PRESTO("\"", Casing.UNCHANGED), REDSHIFT("\"", Casing.TO_LOWER), - SNOWFLAKE("\""), - SPARKSQL("`", BACKSLASH_ESCAPE) { + SNOWFLAKE("\"", BACKSLASH), + SPARKSQL("`", BACKSLASH) { @Override public String getCompositePrefix(SqlTransformer.Case caze) { @@ -78,16 +78,16 @@ public String getFieldPrefix(String fieldName) { private final TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow; private final TriFunction, Supplier, SqlTransformer.Case, String> batchOtherRows; private final Function lastBatchRow; - private final boolean backslashEscape; + private final QuoteEscape quoteEscape; private static final String DEFAULT_BEFORE_EACH_BATCH_PREFIX = " "; - SqlDialect(String sqlQuoteIdentifier, Casing casing, boolean backslashEscape, + SqlDialect(String sqlQuoteIdentifier, Casing casing, QuoteEscape quoteEscape, TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow, TriFunction, Supplier, SqlTransformer.Case, String> batchOtherRows, Function lastBatchRow) { this.sqlQuoteIdentifier = sqlQuoteIdentifier; this.unquotedCasing = casing; - this.backslashEscape = backslashEscape; + this.quoteEscape = quoteEscape; this.batchFirstRow = batchFirstRow; this.batchOtherRows = batchOtherRows; this.lastBatchRow = lastBatchRow; @@ -95,7 +95,7 @@ public String getFieldPrefix(String fieldName) { SqlDialect(String sqlQuoteIdentifier, Casing casing, TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow, TriFunction, Supplier, SqlTransformer.Case, String> batchOtherRows, Function lastBatchRow) { - this(sqlQuoteIdentifier, casing, false, batchFirstRow, batchOtherRows, lastBatchRow); + this(sqlQuoteIdentifier, casing, QuoteEscape.DOUBLE, batchFirstRow, batchOtherRows, lastBatchRow); } SqlDialect(String sqlQuoteIdentifier, TriFunction, Supplier, SqlTransformer.Case, String> batchFirstRow, @@ -103,19 +103,19 @@ public String getFieldPrefix(String fieldName) { this(sqlQuoteIdentifier, DEFAULT_CASING, batchFirstRow, batchOtherRows, lastBatchRow); } - SqlDialect(String sqlQuoteIdentifier, Casing casing, boolean backslashEscape) { - this(sqlQuoteIdentifier, casing, backslashEscape, + SqlDialect(String sqlQuoteIdentifier, Casing casing, QuoteEscape quoteEscape) { + this(sqlQuoteIdentifier, casing, quoteEscape, DEFAULT_FIRST_ROW, DEFAULT_OTHER_ROWS, s -> ""); } SqlDialect(String sqlQuoteIdentifier, Casing casing) { - this(sqlQuoteIdentifier, casing, false); + this(sqlQuoteIdentifier, casing, QuoteEscape.DOUBLE); } - SqlDialect(String sqlQuoteIdentifier, boolean backslashEscape) { - this(sqlQuoteIdentifier, DEFAULT_CASING, backslashEscape); + SqlDialect(String sqlQuoteIdentifier, QuoteEscape quoteEscape) { + this(sqlQuoteIdentifier, DEFAULT_CASING, quoteEscape); } SqlDialect(String sqlQuoteIdentifier) { @@ -164,14 +164,27 @@ public String getArrayEnd() { return "]"; } + /** + * Whether string literals of this dialect escape the quote with a backslash instead of doubling it. + * + * @deprecated use {@link #escapeStringLiteral(String, char)}, which also escapes backslashes + */ + @Deprecated + public boolean escapesQuoteWithBackslash() { + return quoteEscape == QuoteEscape.BACKSLASH; + } + /** * Escapes a value placed inside a string literal delimited by {@code quote}. - * Most dialects double the quote ({@code 'It''s'}) as standard SQL requires. Dialects created with - * {@code BACKSLASH_ESCAPE} put a backslash before the quote and before a literal backslash + * Most dialects double the quote ({@code 'It''s'}) as standard SQL requires. Dialects declared with + * {@code QuoteEscape.BACKSLASH} put a backslash before the quote and before a literal backslash * ({@code 'It\'s'}, {@code 'C:\\temp'}). + *

+ * This assumes the server's default behaviour: MySQL and MariaDB running with the + * {@code NO_BACKSLASH_ESCAPES} SQL mode treat backslashes literally and need the standard doubling instead. */ public String escapeStringLiteral(String value, char quote) { - return escape(value, quote, backslashEscape ? '\\' : quote); + return escape(value, quote, quoteEscape == QuoteEscape.BACKSLASH ? '\\' : quote); } /** @@ -190,9 +203,17 @@ static String escape(String value, char quote, char escape) { return result.toString(); } - static class AuxiliaryConstants { - static final boolean BACKSLASH_ESCAPE = true; + /** + * How a dialect escapes the quote character inside a string literal. + */ + enum QuoteEscape { + /** {@code 'It''s'} */ + DOUBLE, + /** {@code 'It's'}, a literal backslash is escaped too */ + BACKSLASH + } + static class AuxiliaryConstants { static final TriFunction, Supplier, SqlTransformer.Case, String> DEFAULT_FIRST_ROW = (supplier, supplier2, caze) -> { final String insertAll = INSERT_INTO.getValue(caze) + " "; final String values = LINE_SEPARATOR + VALUES.getValue(caze) + " "; diff --git a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java index 160f9800a..0926bb47f 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlTransformer.java @@ -115,7 +115,7 @@ private String addValues(@Nullable IN input, Field[] fields, Boolean isRoo dialect.getFieldPrefix(fields[i].getName()); if (!fieldPrefix.isEmpty() && !isRoot) { - result.add(quote + dialect.getFieldPrefix(fields[i].getName()) + quote); + result.add(quote + escapeStringLiteral(fieldPrefix) + quote); } } @@ -213,14 +213,22 @@ private String handleObject(@Nullable Object value) { } else { String strValue = value.toString(); final boolean quoteRequired = !(value instanceof Number) && !(value instanceof Boolean); - String res = dialect == null - ? SqlDialect.escape(strValue, quote, '\\') - : dialect.escapeStringLiteral(strValue, quote); + String res = escapeStringLiteral(strValue); return quoteRequired ? quote + res + quote : res; } } } + /** + * Without a dialect, the quote is prefixed with a backslash and backslashes are left alone. + */ + private String escapeStringLiteral(String value) { + if (dialect != null) { + return dialect.escapeStringLiteral(value, quote); + } + return value.replace(String.valueOf(quote), "\\" + quote); + } + private String handlePrimitivesInArray(Class componentType, Object value) { StringJoiner joiner = new StringJoiner(", "); if (componentType == byte.class) { diff --git a/src/test/java/net/datafaker/formats/SqlTest.java b/src/test/java/net/datafaker/formats/SqlTest.java index af5e5792e..456308fdb 100644 --- a/src/test/java/net/datafaker/formats/SqlTest.java +++ b/src/test/java/net/datafaker/formats/SqlTest.java @@ -617,6 +617,9 @@ private static Stream quoteEscapeDialects() { of(SqlDialect.MYSQL, "'O\\'Brien'"), of(SqlDialect.MARIADB, "'O\\'Brien'"), of(SqlDialect.BIGQUERY, "'O\\'Brien'"), + of(SqlDialect.CLICKHOUSE, "'O\\'Brien'"), + of(SqlDialect.INFOBRIGHT, "'O\\'Brien'"), + of(SqlDialect.SNOWFLAKE, "'O\\'Brien'"), of(SqlDialect.SPARKSQL, "'O\\'Brien'") ); } @@ -637,6 +640,9 @@ private static Stream backslashEscapeDialects() { of(SqlDialect.MYSQL, "'C:\\\\temp\\\\'"), of(SqlDialect.MARIADB, "'C:\\\\temp\\\\'"), of(SqlDialect.BIGQUERY, "'C:\\\\temp\\\\'"), + of(SqlDialect.CLICKHOUSE, "'C:\\\\temp\\\\'"), + of(SqlDialect.INFOBRIGHT, "'C:\\\\temp\\\\'"), + of(SqlDialect.SNOWFLAKE, "'C:\\\\temp\\\\'"), of(SqlDialect.SPARKSQL, "'C:\\\\temp\\\\'") ); } @@ -648,4 +654,27 @@ void escapesBackslashBeforeQuote() { assertThat(transformer.generate(schema, 1)).endsWith("VALUES ('a\\\\\\'b');"); } + + @Test + void withoutDialectOnlyTheQuoteIsEscaped() { + Schema schema = Schema.of(field("text", () -> "C:\\temp\\O'Brien")); + SqlTransformer transformer = SqlTransformer.builder().build(); + + assertThat(transformer.generate(schema, 1)).endsWith("VALUES ('C:\\temp\\O\\'Brien');"); + } + + @Test + void escapesSparkSqlFieldPrefix() { + Schema schema = Schema.of( + compositeField("outer", new Field[]{field("it's", () -> "x"), field("a\\b", () -> "y")})); + SqlTransformer transformer = SqlTransformer.builder().dialect(SqlDialect.SPARKSQL).build(); + + assertThat(transformer.generate(schema, 1)).contains("'it\\'s', 'x'", "'a\\\\b', 'y'"); + } + + @Test + void deprecatedEscapesQuoteWithBackslashStillReports() { + assertThat(SqlDialect.MYSQL.escapesQuoteWithBackslash()).isTrue(); + assertThat(SqlDialect.POSTGRES.escapesQuoteWithBackslash()).isFalse(); + } } From c6e2e8b3f553a7d13f17ee8447302f1f024f29a3 Mon Sep 17 00:00:00 2001 From: ItsDeadlyProgrammer Date: Sun, 25 Oct 2026 08:04:02 +0530 Subject: [PATCH 4/4] Remove unreleased escapesQuoteWithBackslash and fix javadoc example --- .../datafaker/transformations/sql/SqlDialect.java | 12 +----------- src/test/java/net/datafaker/formats/SqlTest.java | 6 ------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java index faa9e3c35..29b813fee 100644 --- a/src/main/java/net/datafaker/transformations/sql/SqlDialect.java +++ b/src/main/java/net/datafaker/transformations/sql/SqlDialect.java @@ -164,16 +164,6 @@ public String getArrayEnd() { return "]"; } - /** - * Whether string literals of this dialect escape the quote with a backslash instead of doubling it. - * - * @deprecated use {@link #escapeStringLiteral(String, char)}, which also escapes backslashes - */ - @Deprecated - public boolean escapesQuoteWithBackslash() { - return quoteEscape == QuoteEscape.BACKSLASH; - } - /** * Escapes a value placed inside a string literal delimited by {@code quote}. * Most dialects double the quote ({@code 'It''s'}) as standard SQL requires. Dialects declared with @@ -209,7 +199,7 @@ static String escape(String value, char quote, char escape) { enum QuoteEscape { /** {@code 'It''s'} */ DOUBLE, - /** {@code 'It's'}, a literal backslash is escaped too */ + /** {@code 'It\'s'}, a literal backslash is escaped too */ BACKSLASH } diff --git a/src/test/java/net/datafaker/formats/SqlTest.java b/src/test/java/net/datafaker/formats/SqlTest.java index 456308fdb..3d8b9bae0 100644 --- a/src/test/java/net/datafaker/formats/SqlTest.java +++ b/src/test/java/net/datafaker/formats/SqlTest.java @@ -671,10 +671,4 @@ void escapesSparkSqlFieldPrefix() { assertThat(transformer.generate(schema, 1)).contains("'it\\'s', 'x'", "'a\\\\b', 'y'"); } - - @Test - void deprecatedEscapesQuoteWithBackslashStillReports() { - assertThat(SqlDialect.MYSQL.escapesQuoteWithBackslash()).isTrue(); - assertThat(SqlDialect.POSTGRES.escapesQuoteWithBackslash()).isFalse(); - } }