-
Notifications
You must be signed in to change notification settings - Fork 135
feat: support multiple PostgreSQL transaction options #1949
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1d8e6c0
8868723
2cc7ac2
6ff3a60
9e0581d
5521db4
e27c841
93f6f5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,13 +21,16 @@ | |
import com.google.cloud.spanner.SpannerExceptionFactory; | ||
import com.google.cloud.spanner.TimestampBound; | ||
import com.google.cloud.spanner.TimestampBound.Mode; | ||
import com.google.cloud.spanner.connection.PgTransactionMode.AccessMode; | ||
import com.google.cloud.spanner.connection.PgTransactionMode.IsolationLevel; | ||
import com.google.common.base.Function; | ||
import com.google.common.base.Preconditions; | ||
import com.google.protobuf.Duration; | ||
import com.google.protobuf.util.Durations; | ||
import com.google.spanner.v1.RequestOptions.Priority; | ||
import java.util.EnumSet; | ||
import java.util.HashMap; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.regex.Matcher; | ||
|
@@ -85,6 +88,53 @@ public Boolean convert(String value) { | |
} | ||
} | ||
|
||
/** Converter from string to {@link Boolean} */ | ||
static class PgBooleanConverter implements ClientSideStatementValueConverter<Boolean> { | ||
|
||
public PgBooleanConverter(String allowedValues) {} | ||
|
||
@Override | ||
public Class<Boolean> getParameterClass() { | ||
return Boolean.class; | ||
} | ||
|
||
@Override | ||
public Boolean convert(String value) { | ||
if (value == null) { | ||
return null; | ||
} | ||
if (value.length() > 1 | ||
&& ((value.startsWith("'") && value.endsWith("'")) | ||
|| (value.startsWith("\"") && value.endsWith("\"")))) { | ||
value = value.substring(1, value.length() - 1); | ||
} | ||
if ("true".equalsIgnoreCase(value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should also trim the string to consider the cases like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (See also above). PostgreSQL does not consider that to be a valid boolean value. When the value is quoted with either single or double quotes, it may not contain any spaces. |
||
|| "tru".equalsIgnoreCase(value) | ||
|| "tr".equalsIgnoreCase(value) | ||
|| "t".equalsIgnoreCase(value) | ||
|| "on".equalsIgnoreCase(value) | ||
|| "1".equalsIgnoreCase(value) | ||
|| "yes".equalsIgnoreCase(value) | ||
|| "ye".equalsIgnoreCase(value) | ||
|| "y".equalsIgnoreCase(value)) { | ||
return Boolean.TRUE; | ||
} | ||
if ("false".equalsIgnoreCase(value) | ||
|| "fals".equalsIgnoreCase(value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that the values like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strangely enough, they are. I've tried a multitude of different statements with a real PG instance. See below for the results: knut-test-db=# set default_transaction_read_only to "on";
SET
knut-test-db=# set default_transaction_read_only to tr;
SET
knut-test-db=# set default_transaction_read_only to ' true ';
ERROR: parameter "default_transaction_read_only" requires a Boolean value
knut-test-db=# set default_transaction_read_only to "true";
SET
knut-test-db=# set default_transaction_read_only to 'true';
SET
knut-test-db=# set default_transaction_read_only to ye;
SET
knut-test-db=# set default_transaction_read_only to 'ye';
SET So TLDR:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's strange because once I tested inserting boolean values in some table and the results were totally opposite :) Btw thanks for clarifying |
||
|| "fal".equalsIgnoreCase(value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add support for 'ON', 'OFF', 'yes', 'no', 0 and 1 while converting the boolean values? For the Psycopg2, adding only 'ON' and 'OFF' will do but we can add 'yes', 'no', 0 and 1 to make it fully generic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, added for:
Including all unique prefixes (i.e. |
||
|| "fa".equalsIgnoreCase(value) | ||
|| "f".equalsIgnoreCase(value) | ||
|| "off".equalsIgnoreCase(value) | ||
|| "of".equalsIgnoreCase(value) | ||
|| "0".equalsIgnoreCase(value) | ||
|| "no".equalsIgnoreCase(value) | ||
|| "n".equalsIgnoreCase(value)) { | ||
return Boolean.FALSE; | ||
} | ||
return null; | ||
} | ||
} | ||
|
||
/** Converter from string to {@link Duration}. */ | ||
static class DurationConverter implements ClientSideStatementValueConverter<Duration> { | ||
private final Pattern allowedValues; | ||
|
@@ -286,16 +336,39 @@ public TransactionMode convert(String value) { | |
} | ||
} | ||
|
||
static class PgTransactionIsolationConverter | ||
implements ClientSideStatementValueConverter<IsolationLevel> { | ||
private final CaseInsensitiveEnumMap<IsolationLevel> values = | ||
new CaseInsensitiveEnumMap<>(IsolationLevel.class, IsolationLevel::getShortStatementString); | ||
|
||
public PgTransactionIsolationConverter(String allowedValues) {} | ||
|
||
@Override | ||
public Class<IsolationLevel> getParameterClass() { | ||
return IsolationLevel.class; | ||
} | ||
|
||
@Override | ||
public IsolationLevel convert(String value) { | ||
// Isolation level may contain multiple spaces. | ||
String valueWithSingleSpaces = value.replaceAll("\\s+", " "); | ||
if (valueWithSingleSpaces.length() > 1 | ||
&& ((valueWithSingleSpaces.startsWith("'") && valueWithSingleSpaces.endsWith("'")) | ||
|| (valueWithSingleSpaces.startsWith("\"") | ||
&& valueWithSingleSpaces.endsWith("\"")))) { | ||
valueWithSingleSpaces = | ||
valueWithSingleSpaces.substring(1, valueWithSingleSpaces.length() - 1); | ||
} | ||
return values.get(valueWithSingleSpaces); | ||
} | ||
} | ||
|
||
/** | ||
* Converter for converting string values to {@link PgTransactionMode} values. Includes no-op | ||
* handling of setting the isolation level of the transaction to default or serializable. | ||
*/ | ||
static class PgTransactionModeConverter | ||
implements ClientSideStatementValueConverter<PgTransactionMode> { | ||
private final CaseInsensitiveEnumMap<PgTransactionMode> values = | ||
new CaseInsensitiveEnumMap<>( | ||
PgTransactionMode.class, PgTransactionMode::getStatementString); | ||
|
||
PgTransactionModeConverter() {} | ||
|
||
public PgTransactionModeConverter(String allowedValues) {} | ||
|
@@ -307,9 +380,49 @@ public Class<PgTransactionMode> getParameterClass() { | |
|
||
@Override | ||
public PgTransactionMode convert(String value) { | ||
PgTransactionMode mode = new PgTransactionMode(); | ||
// Transaction mode may contain multiple spaces. | ||
String valueWithSingleSpaces = value.replaceAll("\\s+", " "); | ||
return values.get(valueWithSingleSpaces); | ||
String valueWithSingleSpaces = | ||
value.replaceAll("\\s+", " ").toLowerCase(Locale.ENGLISH).trim(); | ||
int currentIndex = 0; | ||
while (currentIndex < valueWithSingleSpaces.length()) { | ||
// This will use the last access mode and isolation level that is encountered in the string. | ||
// This is consistent with the behavior of PostgreSQL, which also allows multiple modes to | ||
// be specified in one string, and will use the last one that is encountered. | ||
if (valueWithSingleSpaces.substring(currentIndex).startsWith("read only")) { | ||
currentIndex += "read only".length(); | ||
mode.setAccessMode(AccessMode.READ_ONLY_TRANSACTION); | ||
} else if (valueWithSingleSpaces.substring(currentIndex).startsWith("read write")) { | ||
currentIndex += "read write".length(); | ||
mode.setAccessMode(AccessMode.READ_WRITE_TRANSACTION); | ||
} else if (valueWithSingleSpaces | ||
.substring(currentIndex) | ||
.startsWith("isolation level serializable")) { | ||
currentIndex += "isolation level serializable".length(); | ||
mode.setIsolationLevel(IsolationLevel.ISOLATION_LEVEL_SERIALIZABLE); | ||
} else if (valueWithSingleSpaces | ||
.substring(currentIndex) | ||
.startsWith("isolation level default")) { | ||
currentIndex += "isolation level default".length(); | ||
mode.setIsolationLevel(IsolationLevel.ISOLATION_LEVEL_DEFAULT); | ||
} else { | ||
return null; | ||
} | ||
// Skip space and/or comma that may separate multiple transaction modes. | ||
if (currentIndex < valueWithSingleSpaces.length() | ||
&& valueWithSingleSpaces.charAt(currentIndex) == ' ') { | ||
currentIndex++; | ||
} | ||
if (currentIndex < valueWithSingleSpaces.length() | ||
&& valueWithSingleSpaces.charAt(currentIndex) == ',') { | ||
currentIndex++; | ||
} | ||
if (currentIndex < valueWithSingleSpaces.length() | ||
&& valueWithSingleSpaces.charAt(currentIndex) == ' ') { | ||
currentIndex++; | ||
} | ||
} | ||
return mode; | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Double Quotes("") are used to identify objects inside the database like column names, table names etc. So,
"true"
shouldn't workThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it strange, but it actually allowed (see above)