Skip to content
Open
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 @@ -148,7 +148,7 @@ public class ConnectionHandler implements Runnable {
private Connection spannerConnection;
private DatabaseId databaseId;
private WellKnownClient wellKnownClient = WellKnownClient.UNSPECIFIED;
private boolean hasDeterminedClientUsingQuery;
private int autoDetectCount;

/**
* List of PARSE messages that we received before auto-detecting the client. This list can be used
Expand Down Expand Up @@ -967,9 +967,8 @@ public void setWellKnownClient(WellKnownClient wellKnownClient) {
* executed.
*/
public void maybeDetermineWellKnownClient(Statement statement) {
if (!this.hasDeterminedClientUsingQuery) {
if (this.wellKnownClient == WellKnownClient.UNSPECIFIED
&& getServer().getOptions().shouldAutoDetectClient()) {
if (this.wellKnownClient == WellKnownClient.UNSPECIFIED && this.autoDetectCount < 100) {
if (getServer().getOptions().shouldAutoDetectClient()) {
setWellKnownClient(
ClientAutoDetector.detectClient(
skippedAutoDetectParseMessages, ImmutableList.of(statement)));
Expand All @@ -985,8 +984,7 @@ && getServer().getOptions().shouldAutoDetectClient()) {
}
maybeSetApplicationName();
skippedAutoDetectParseMessages.clear();
// Make sure that we only try to detect the client once.
this.hasDeterminedClientUsingQuery = true;
this.autoDetectCount++;
}
}

Expand All @@ -997,7 +995,7 @@ && getServer().getOptions().shouldAutoDetectClient()) {
* messages.
*/
public void maybeDetermineWellKnownClient(ParseMessage parseMessage) {
if (!this.hasDeterminedClientUsingQuery) {
if (this.wellKnownClient == WellKnownClient.UNSPECIFIED && this.autoDetectCount < 100) {
// We skip up to 10 Parse messages before forcing the connection to detect a client (or not).
// This is a safety measure against clients that might send a very large number of Parse
// messages with client-side statements directly after connecting. This could in worst case
Expand All @@ -1009,15 +1007,13 @@ public void maybeDetermineWellKnownClient(ParseMessage parseMessage) {
&& skippedAutoDetectParseMessages.size() < 10) {
skippedAutoDetectParseMessages.add(parseMessage);
} else {
if (this.wellKnownClient == WellKnownClient.UNSPECIFIED
&& getServer().getOptions().shouldAutoDetectClient()) {
if (getServer().getOptions().shouldAutoDetectClient()) {
setWellKnownClient(
ClientAutoDetector.detectClient(skippedAutoDetectParseMessages, parseMessage));
}
maybeSetApplicationName();
skippedAutoDetectParseMessages.clear();
// Make sure that we only try to detect the client once.
this.hasDeterminedClientUsingQuery = true;
this.autoDetectCount++;
}
}
}
Expand Down Expand Up @@ -1052,7 +1048,7 @@ List<ParseMessage> getSkippedAutoDetectParseMessages() {

@VisibleForTesting
boolean isHasDeterminedClientUsingQuery() {
return this.hasDeterminedClientUsingQuery;
return this.wellKnownClient != WellKnownClient.UNSPECIFIED || this.autoDetectCount >= 10;
}

/** Status of a {@link ConnectionHandler} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public static class Builder {

Builder() {}

Builder setEnvironment(Map<String, String> environment) {
public Builder setEnvironment(Map<String, String> environment) {
this.environment = Preconditions.checkNotNull(environment);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ public class PgAttribute implements PgCatalogTable {

public static final String PG_ATTRIBUTE_CTE =
"pg_attribute as (\n"
+ "select '''\"' || table_schema || '\".\"' || table_name || '\"''' as attrelid,\n"
+ "select (ABS(spanner.farm_fingerprint(table_schema || '.' || table_name)) % 2147483647) as attrelid,\n"
+ " column_name as attname,\n"
+ " case regexp_replace(c.spanner_type, '\\(.*\\)', '')\n"
+ " when 'boolean' then 16\n"
Expand Down Expand Up @@ -712,7 +712,7 @@ public class PgAttribute implements PgCatalogTable {
+ " c.spanner_type\n"
+ "from information_schema.columns c\n"
+ "union all\n"
+ "select '''\"' || i.table_schema || '\".\"' || i.table_name || '\".\"' || i.index_name || '\"''' as attrelid,\n"
+ "select (ABS(spanner.farm_fingerprint(i.table_schema || '.' || i.table_name || '.' || i.index_name)) % 2147483647) as attrelid,\n"
+ " i.column_name as attname,\n"
+ " case regexp_replace(c.spanner_type, '\\(.*\\)', '')\n"
+ " when 'boolean' then 16\n"
Expand Down Expand Up @@ -770,8 +770,8 @@ public class PgAttrdef implements PgCatalogTable {

public static final String PG_ATTRDEF_CTE =
"pg_attrdef as (\n"
+ "select '''\"' || table_schema || '\".\"' || table_name || '\".\"' || column_name || '\"''' as oid,\n"
+ " '''\"' || table_schema || '\".\"' || table_name || '\"''' as adrelid,\n"
+ "select (ABS(spanner.farm_fingerprint(table_schema || '.' || table_name || '.' || column_name)) % 2147483647) as oid,\n"
+ " (ABS(spanner.farm_fingerprint(table_schema || '.' || table_name)) % 2147483647) as adrelid,\n"
+ " ordinal_position as adnum,\n"
+ " coalesce(column_default, generation_expression) as adbin\n"
+ "from information_schema.columns c\n"
Expand All @@ -788,17 +788,17 @@ public class PgConstraint implements PgCatalogTable {
public static final String PG_CONSTRAINT_CTE =
"pg_constraint as (\n"
+ "select\n"
+ " '''\"' || tc.constraint_schema || '\".\"' || tc.constraint_name || '\"''' as oid,\n"
+ " (ABS(spanner.farm_fingerprint(tc.constraint_schema || '.' || tc.constraint_name)) % 2147483647) as oid,\n"
+ " tc.constraint_name as conname, 2200 as connamespace,\n"
+ " case tc.constraint_type\n"
+ " when 'PRIMARY KEY' then 'p'\n"
+ " when 'CHECK' then 'c'\n"
+ " when 'FOREIGN KEY' then 'f'\n"
+ " else ''\n"
+ " end as contype, false as condeferrable, false as condeferred, true as convalidated,\n"
+ " '''\"' || tc.table_schema || '\".\"' || tc.table_name || '\"''' as conrelid,\n"
+ " (ABS(spanner.farm_fingerprint(tc.table_schema || '.' || tc.table_name)) % 2147483647) as conrelid,\n"
+ " 0::bigint as contypid, '0'::varchar as conindid, '0'::varchar as conparentid,\n"
+ " '''\"' || uc.table_schema || '\".\"' || uc.table_name || '\"''' as confrelid,\n"
+ " (ABS(spanner.farm_fingerprint(uc.table_schema || '.' || uc.table_name)) % 2147483647) as confrelid,\n"
+ " case rc.update_rule\n"
+ " when 'CASCADE' then 'c'\n"
+ " when 'NO ACTION' then 'a'\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
import com.google.cloud.spanner.pgadapter.statements.BackendConnection.ConnectionState;
import com.google.cloud.spanner.pgadapter.utils.ClientAutoDetector.WellKnownClient;
import com.google.cloud.spanner.pgadapter.utils.QueryPartReplacer;
import com.google.cloud.spanner.pgadapter.wireprotocol.BindMessage;
import com.google.cloud.spanner.pgadapter.wireprotocol.ControlMessage.ManuallyCreatedToken;
import com.google.cloud.spanner.pgadapter.wireprotocol.DescribeMessage;
Expand Down Expand Up @@ -117,11 +118,25 @@ public void execute() throws Exception {
/** Replaces any known unsupported query (e.g. JDBC metadata queries). */
static Tuple<Boolean, String> replaceKnownUnsupportedQueries(
WellKnownClient client, OptionsMetadata options, String sql) {
boolean replaced = false;
if ((options.isReplaceJdbcMetadataQueries() || client == WellKnownClient.JDBC)
&& JdbcMetadataStatementHelper.isPotentialJdbcMetadataStatement(sql)) {
return Tuple.of(Boolean.TRUE, JdbcMetadataStatementHelper.replaceJdbcMetadataStatement(sql));
sql = JdbcMetadataStatementHelper.replaceJdbcMetadataStatement(sql);
replaced = true;
}
return Tuple.of(Boolean.FALSE, sql);
if (client != null) {
for (QueryPartReplacer replacer : client.getQueryPartReplacements()) {
Tuple<String, QueryPartReplacer.ReplacementStatus> result = replacer.replace(sql);
if (!result.x().equals(sql)) {
sql = result.x();
replaced = true;
}
if (result.y() == QueryPartReplacer.ReplacementStatus.STOP) {
break;
}
}
}
return Tuple.of(replaced, sql);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,34 @@ public ImmutableList<QueryPartReplacer> getDdlReplacements() {
+ ")"));
}
},
ADBC {
private final Pattern adbcTypeQuery =
Pattern.compile("(?s).*pg_type.*WHERE.*typreceive.*typsend.*", Pattern.CASE_INSENSITIVE);

@Override
boolean isClient(List<String> orderedParameterKeys, Map<String, String> parameters) {
return false;
}

@Override
boolean isClient(List<ParseMessage> skippedParseMessages, ParseMessage parseMessage) {
return parseMessage.getSql() != null && adbcTypeQuery.matcher(parseMessage.getSql()).find();
}

@Override
boolean isClient(List<ParseMessage> skippedParseMessages, List<Statement> statements) {
return !statements.isEmpty() && adbcTypeQuery.matcher(statements.get(0).getSql()).find();
}

@Override
public ImmutableList<QueryPartReplacer> getQueryPartReplacements() {
return ImmutableList.of(
RegexQueryPartReplacer.replace(
Pattern.compile("typreceive\\s*!=\\s*0"), "typreceive != '0'::varchar"),
RegexQueryPartReplacer.replace(
Pattern.compile("typsend\\s*!=\\s*0"), "typsend != '0'::varchar"));
}
},
PGX {
@Override
boolean isClient(List<String> orderedParameterKeys, Map<String, String> parameters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public enum Action {
/** Format a log message by prepending the current thread name and method to the message. */
public static Supplier<String> format(String method, Supplier<String> message) {
return () ->
String.format("[%s]: [%s] " + message.get(), Thread.currentThread().getName(), method);
String.format("[%s]: [%s] %s", Thread.currentThread().getName(), method, message.get());
}

/** Create a log message with the current thread name, method and action. */
Expand All @@ -40,6 +40,6 @@ public static Supplier<String> format(String method, Action action) {
public static Supplier<String> format(String method, Action action, Supplier<String> message) {
return () ->
String.format(
"[%s]: [%s] [%s] " + message.get(), Thread.currentThread().getName(), method, action);
"[%s]: [%s] [%s] %s", Thread.currentThread().getName(), method, action, message.get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ public void testMaybeDetermineWellKnownClient_stopsSkippingParseMessagesAfter10M
// UNSPECIFIED.
connectionHandler.maybeDetermineWellKnownClient(parseMessage);

assertTrue(connectionHandler.isHasDeterminedClientUsingQuery());
assertFalse(connectionHandler.isHasDeterminedClientUsingQuery());
assertTrue(connectionHandler.getSkippedAutoDetectParseMessages().isEmpty());
}

Expand All @@ -665,7 +665,10 @@ public void testBuildConnectionUrl() {
== null);

OptionsMetadata options =
OptionsMetadata.newBuilder().setCredentials(NoCredentials.getInstance()).build();
OptionsMetadata.newBuilder()
.setCredentials(NoCredentials.getInstance())
.setEnvironment(ImmutableMap.of())
.build();
// Check that the dialect is included in the connection URL. This is required to support the
// 'autoConfigEmulator' property.
assertEquals(
Expand Down Expand Up @@ -694,11 +697,16 @@ public void testBuildConnectionUrl() {
.setProject("test-project")
.setInstance("test-instance")
.setDatabase("test-database")
.setEnvironment(ImmutableMap.of())
.build(),
buildProperties(ImmutableMap.of()),
ImmutableMap.of()));
// Enable the autoConfigEmulator flag through the options builder.
OptionsMetadata emulatorOptions = OptionsMetadata.newBuilder().autoConfigureEmulator().build();
OptionsMetadata emulatorOptions =
OptionsMetadata.newBuilder()
.autoConfigureEmulator()
.setEnvironment(ImmutableMap.of())
.build();
assertEquals(
"cloudspanner:/projects/my-project/instances/my-instance/databases/my-database;userAgent=pg-adapter;autoConfigEmulator=true;defaultSequenceKind=bit_reversed_positive;dialect=postgresql",
buildConnectionURL(
Expand Down Expand Up @@ -770,7 +778,10 @@ public void testBuildConnectionUrl() {
"true",
() -> {
OptionsMetadata optionsWithSystemProperty =
OptionsMetadata.newBuilder().setCredentials(NoCredentials.getInstance()).build();
OptionsMetadata.newBuilder()
.setCredentials(NoCredentials.getInstance())
.setEnvironment(ImmutableMap.of())
.build();
assertEquals(
"cloudspanner:/projects/my-project/instances/my-instance/databases/my-database;userAgent=pg-adapter;defaultSequenceKind=bit_reversed_positive;useVirtualThreads=true;dialect=postgresql",
buildConnectionURL(
Expand All @@ -784,7 +795,10 @@ public void testBuildConnectionUrl() {
"true",
() -> {
OptionsMetadata optionsWithSystemProperty =
OptionsMetadata.newBuilder().setCredentials(NoCredentials.getInstance()).build();
OptionsMetadata.newBuilder()
.setCredentials(NoCredentials.getInstance())
.setEnvironment(ImmutableMap.of())
.build();
assertEquals(
"cloudspanner:/projects/my-project/instances/my-instance/databases/my-database;userAgent=pg-adapter;defaultSequenceKind=bit_reversed_positive;useVirtualGrpcTransportThreads=true;dialect=postgresql",
buildConnectionURL(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,19 @@ public void testBuildConnectionUrlWithFullPath() {

assertEquals(
"cloudspanner:/projects/test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter;credentials=credentials.json",
new OptionsMetadata(new String[] {"-c", "credentials.json"})
new OptionsMetadata(
ImmutableMap.of(),
System.getProperty("os.name"),
DEFAULT_STARTUP_TIMEOUT,
new String[] {"-c", "credentials.json"})
.buildConnectionURL(
"projects/test-project/instances/test-instance/databases/test-database"));
assertEquals(
"cloudspanner:/projects/test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter;credentials=credentials.json",
new OptionsMetadata(
ImmutableMap.of(),
System.getProperty("os.name"),
DEFAULT_STARTUP_TIMEOUT,
new String[] {
"-p", "test-project", "-i", "test-instance", "-c", "credentials.json"
})
Expand Down Expand Up @@ -171,7 +178,11 @@ public void testBuildConnectionUrlWithDefaultProjectId() {
== null);

OptionsMetadata useDefaultProjectIdOptions =
new OptionsMetadata(new String[] {"-i", "test-instance", "-c", "credentials.json"}) {
new OptionsMetadata(
ImmutableMap.of(),
System.getProperty("os.name"),
DEFAULT_STARTUP_TIMEOUT,
new String[] {"-i", "test-instance", "-c", "credentials.json"}) {
@Override
String getDefaultProjectId() {
return "custom-test-project";
Expand All @@ -181,7 +192,11 @@ String getDefaultProjectId() {
"cloudspanner:/projects/custom-test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter;credentials=credentials.json",
useDefaultProjectIdOptions.buildConnectionURL("test-database"));
OptionsMetadata noProjectIdOptions =
new OptionsMetadata(new String[] {"-i", "test-instance", "-c", "credentials.json"}) {
new OptionsMetadata(
ImmutableMap.of(),
System.getProperty("os.name"),
DEFAULT_STARTUP_TIMEOUT,
new String[] {"-i", "test-instance", "-c", "credentials.json"}) {
@Override
String getDefaultProjectId() {
return null;
Expand All @@ -202,15 +217,23 @@ public void testBuildConnectionUrlWithDefaultCredentials() {
== null);

OptionsMetadata useDefaultCredentials =
new OptionsMetadata(new String[] {"-p", "test-project", "-i", "test-instance"}) {
new OptionsMetadata(
ImmutableMap.of(),
System.getProperty("os.name"),
DEFAULT_STARTUP_TIMEOUT,
new String[] {"-p", "test-project", "-i", "test-instance"}) {
@Override
void tryGetDefaultCredentials() {}
};
assertEquals(
"cloudspanner:/projects/test-project/instances/test-instance/databases/test-database;userAgent=pg-adapter",
useDefaultCredentials.buildConnectionURL("test-database"));
OptionsMetadata noDefaultCredentialsOptions =
new OptionsMetadata(new String[] {"-p", "test-project", "-i", "test-instance"}) {
new OptionsMetadata(
ImmutableMap.of(),
System.getProperty("os.name"),
DEFAULT_STARTUP_TIMEOUT,
new String[] {"-p", "test-project", "-i", "test-instance"}) {
@Override
void tryGetDefaultCredentials() throws IOException {
throw new IOException("test exception");
Expand Down
Loading
Loading