From 64e21895a0b0bd02d52fe5c47ef45946fc0862d8 Mon Sep 17 00:00:00 2001 From: Anllick <654610542@qq.com> Date: Wed, 16 Feb 2022 14:55:42 +0800 Subject: [PATCH] Support PostgreSQL and openGauss Update/Delete --- hetu-docs/en/connector/opengauss.md | 91 ++++- hetu-docs/en/connector/postgresql.md | 91 ++++- hetu-docs/zh/connector/opengauss.md | 91 ++++- hetu-docs/zh/connector/postgresql.md | 91 ++++- .../TestOpenGaussIntegrationSmokeTest.java | 150 ++++++++ presto-postgresql/pom.xml | 5 + .../postgresql/BasePostgreSqlClient.java | 327 +++++++++++++++--- .../plugin/postgresql/PostgreSqlClient.java | 69 ++++ .../TestPostgreSqlIntegrationSmokeTest.java | 105 ++++++ 9 files changed, 965 insertions(+), 55 deletions(-) diff --git a/hetu-docs/en/connector/opengauss.md b/hetu-docs/en/connector/opengauss.md index 274cfdb5c..a85f36a98 100644 --- a/hetu-docs/en/connector/opengauss.md +++ b/hetu-docs/en/connector/opengauss.md @@ -45,6 +45,95 @@ Finally, you can access the `hetutb` table in the `public` schema: If you used a different name for your catalog properties file, use that catalog name instead of `opengauss` in the above examples. +## openGauss Update/Delete Support + +### Create openGauss Table + +Example: + +```sql +CREATE TABLE opengauss_table ( + id int, + name varchar(255)); +``` + +### INSERT on openGauss tables + +Example: + +```sql +INSERT INTO opengauss_table + VALUES + (1, 'Jack'), + (2, 'Bob'); +``` + +### UPDATE on openGauss tables + +Example: + +```sql +UPDATE opengauss_table + SET name='Tim' + WHERE id=1; +``` + +Above example updates the column `name`'s value to `Tim` of rows with column `id` having value `1`. + +SELECT result before UPDATE: + +```sql +lk:default> SELECT * FROM opengauss_table; +id | name +----+------ + 1 | Jack + 2 | Bob +(2 rows) +``` + +SELECT result after UPDATE + +```sql +lk:default> SELECT * FROM opengauss_table; +id | name +----+------ +2 | Bob +1 | Tim +(2 rows) +``` + +### DELETE on openGauss tables + +Example: + +```sql +DELETE FROM opengauss_table + WHERE id=2; +``` + +Above example delete the rows with column `id` having value `2`. + +SELECT result before DELETE: + +```sql +lk:default> SELECT * FROM opengauss_table; + id | name +----+------ + 2 | Bob + 1 | Tim +(2 rows) +``` + +SELECT result after DELETE: + +```sql +lk:default> SELECT * FROM opengauss_table; + id | name +----+------ + 1 | Tim +(1 row) +``` + ****Note:**** > - When the compatibility type of the openGuass database is O (DBCOMPATIBILITY = A), the `Date` data type is not supported. @@ -64,4 +153,4 @@ openGauss Connector Limitations The following SQL statements are not yet supported: -[DELETE](../sql/delete.md), [GRANT](../sql/grant.md), [REVOKE](../sql/revoke.md), [SHOW GRANTS](../sql/show-grants.md), [SHOW ROLES](../sql/show-roles.md), [SHOW ROLE GRANTS](../sql/show-role-grants.md) +[GRANT](../sql/grant.md), [REVOKE](../sql/revoke.md), [SHOW GRANTS](../sql/show-grants.md), [SHOW ROLES](../sql/show-roles.md), [SHOW ROLE GRANTS](../sql/show-role-grants.md) diff --git a/hetu-docs/en/connector/postgresql.md b/hetu-docs/en/connector/postgresql.md index 69bf5d58a..0984e2a81 100644 --- a/hetu-docs/en/connector/postgresql.md +++ b/hetu-docs/en/connector/postgresql.md @@ -46,9 +46,98 @@ Finally, you can access the `clicks` table in the `web` schema: If you used a different name for your catalog properties file, use that catalog name instead of `postgresql` in the above examples. +## PostgreSQL Update/Delete Support + +### Create PostgreSQL Table + +Example: + +```sql +CREATE TABLE postgresql_table ( + id int, + name varchar(255)); +``` + +### INSERT on PostgreSQL tables + +Example: + +```sql +INSERT INTO postgresql_table + VALUES + (1, 'Jack'), + (2, 'Bob'); +``` + +### UPDATE on PostgreSQL tables + +Example: + +```sql +UPDATE postgresql_table + SET name='Tim' + WHERE id=1; +``` + +Above example updates the column `name`'s value to `Tim` of rows with column `id` having value `1`. + +SELECT result before UPDATE: + +```sql +lk:default> SELECT * FROM postgresql_table; +id | name +----+------ + 1 | Jack + 2 | Bob +(2 rows) +``` + +SELECT result after UPDATE + +```sql +lk:default> SELECT * FROM postgresql_table; +id | name +----+------ +2 | Bob +1 | Tim +(2 rows) +``` + +### DELETE on PostgreSQL tables + +Example: + +```sql +DELETE FROM postgresql_table + WHERE id=2; +``` + +Above example delete the rows with column `id` having value `2`. + +SELECT result before DELETE: + +```sql +lk:default> SELECT * FROM postgresql_table; + id | name +----+------ + 2 | Bob + 1 | Tim +(2 rows) +``` + +SELECT result after DELETE: + +```sql +lk:default> SELECT * FROM postgresql_table; + id | name +----+------ + 1 | Tim +(1 row) +``` + PostgreSQL Connector Limitations -------------------------------- The following SQL statements are not yet supported: -[DELETE](../sql/delete.md), [GRANT](../sql/grant.md), [REVOKE](../sql/revoke.md), [SHOW GRANTS](../sql/show-grants.md), [SHOW ROLES](../sql/show-roles.md), [SHOW ROLE GRANTS](../sql/show-role-grants.md) +[GRANT](../sql/grant.md), [REVOKE](../sql/revoke.md), [SHOW GRANTS](../sql/show-grants.md), [SHOW ROLES](../sql/show-roles.md), [SHOW ROLE GRANTS](../sql/show-role-grants.md) \ No newline at end of file diff --git a/hetu-docs/zh/connector/opengauss.md b/hetu-docs/zh/connector/opengauss.md index 2e23e0571..212d88f60 100644 --- a/hetu-docs/zh/connector/opengauss.md +++ b/hetu-docs/zh/connector/opengauss.md @@ -41,6 +41,95 @@ openGauss连接器为每个openGauss模式提供一个模式。可通过执行`S 如果对目录属性文件使用不同的名称,请使用该目录名称,而不要使用上述示例中的`opengauss`。 +## openGauss Update/Delete 支持 + +### 使用openGauss连接器创建表 + +示例: + +```sql +CREATE TABLE opengauss_table ( + id int, + name varchar(255)); +``` + +### 对表执行INSERT + +示例: + +```sql +INSERT INTO opengauss_table + VALUES + (1, 'Jack'), + (2, 'Bob'); +``` + +### 对表执行UPDATE + +示例: + +```sql +UPDATE opengauss_table + SET name='Tim' + WHERE id=1; +``` + +上述示例将列`id`中值为`1`所在行的列`name`的值更新为`Tim`。 + +UPDATE前的SELECT结果: + +```sql +lk:default> SELECT * FROM opengauss_table; +id | name +----+------ + 1 | Jack + 2 | Bob +(2 rows) +``` + +UPDATE后的SELECT结果 + +```sql +lk:default> SELECT * FROM opengauss_table; + id | name +----+------ + 2 | Bob + 1 | Tim +(2 rows) +``` + +### 对表执行DELETE + +示例: + +```sql +DELETE FROM opengauss_table + WHERE id=2; +``` + +以上示例删除了值为`2`的列`id`的行。 + +DELETE前的SELECT结果: + +```sql +lk:default> SELECT * FROM opengauss_table; + id | name +----+------ + 2 | Bob + 1 | Tim +(2 rows) +``` + +DELETE后的SELECT结果: + +```sql +lk:default> SELECT * FROM opengauss_table; + id | name +----+------ + 1 | Tim +(1 row) +``` + **注意** > - openGuass数据库兼容类型为O(即DBCOMPATIBILITY = A)时不支持`Date`数据类型。 @@ -59,4 +148,4 @@ openGauss连接器为每个openGauss模式提供一个模式。可通过执行`S 暂不支持以下SQL语句: -[DELETE](../sql/delete.md)、[GRANT](../sql/grant.md)、[REVOKE](../sql/revoke.md)、[SHOW GRANTS](../sql/show-grants.md)、[SHOW ROLES](../sql/show-roles.md)、[SHOW ROLE GRANTS](../sql/show-role-grants.md) \ No newline at end of file +[GRANT](../sql/grant.md)、[REVOKE](../sql/revoke.md)、[SHOW GRANTS](../sql/show-grants.md)、[SHOW ROLES](../sql/show-roles.md)、[SHOW ROLE GRANTS](../sql/show-role-grants.md) \ No newline at end of file diff --git a/hetu-docs/zh/connector/postgresql.md b/hetu-docs/zh/connector/postgresql.md index 31f9bf62a..0b50e7160 100644 --- a/hetu-docs/zh/connector/postgresql.md +++ b/hetu-docs/zh/connector/postgresql.md @@ -41,8 +41,97 @@ PostgreSQL连接器为每个PostgreSQL模式提供一个模式。可通过执行 如果对目录属性文件使用不同的名称,请使用该目录名称,而不要使用上述示例中的`postgresql`。 +## PostgreSQL Update/Delete 支持 + +### 使用PostgreSQL连接器创建表 + +示例: + +```sql +CREATE TABLE postgresql_table ( + id int, + name varchar(255)); +``` + +### 对表执行INSERT + +示例: + +```sql +INSERT INTO postgresql_table + VALUES + (1, 'Jack'), + (2, 'Bob'); +``` + +### 对表执行UPDATE + +示例: + +```sql +UPDATE postgresql_table + SET name='Tim' + WHERE id=1; +``` + +上述示例将列`id`中值为`1`所在行的列`name`的值更新为`Tim`。 + +UPDATE前的SELECT结果: + +```sql +lk:default> SELECT * FROM postgresql_table; +id | name +----+------ + 1 | Jack + 2 | Bob +(2 rows) +``` + +UPDATE后的SELECT结果 + +```sql +lk:default> SELECT * FROM postgresql_table; + id | name +----+------ + 2 | Bob + 1 | Tim +(2 rows) +``` + +### 对表执行DELETE + +示例: + +```sql +DELETE FROM postgresql_table + WHERE id=2; +``` + +以上示例删除了值为`2`的列`id`的行。 + +DELETE前的SELECT结果: + +```sql +lk:default> SELECT * FROM postgresql_table; + id | name +----+------ + 2 | Bob + 1 | Tim +(2 rows) +``` + +DELETE后的SELECT结果: + +```sql +lk:default> SELECT * FROM postgresql_table; + id | name +----+------ + 1 | Tim +(1 row) +``` + ## PostgreSQL连接器限制 暂不支持以下SQL语句: -[DELETE](../sql/delete.md)、[GRANT](../sql/grant.md)、[REVOKE](../sql/revoke.md)、[SHOW GRANTS](../sql/show-grants.md)、[SHOW ROLES](../sql/show-roles.md)、[SHOW ROLE GRANTS](../sql/show-role-grants.md) \ No newline at end of file +[GRANT](../sql/grant.md)、[REVOKE](../sql/revoke.md)、[SHOW GRANTS](../sql/show-grants.md)、[SHOW ROLES](../sql/show-roles.md)、[SHOW ROLE GRANTS](../sql/show-role-grants.md) \ No newline at end of file diff --git a/hetu-opengauss/src/test/java/io/hetu/core/plugin/opengauss/TestOpenGaussIntegrationSmokeTest.java b/hetu-opengauss/src/test/java/io/hetu/core/plugin/opengauss/TestOpenGaussIntegrationSmokeTest.java index 25a2c6af6..4040ab088 100644 --- a/hetu-opengauss/src/test/java/io/hetu/core/plugin/opengauss/TestOpenGaussIntegrationSmokeTest.java +++ b/hetu-opengauss/src/test/java/io/hetu/core/plugin/opengauss/TestOpenGaussIntegrationSmokeTest.java @@ -18,6 +18,8 @@ import io.prestosql.plugin.postgresql.TestPostgreSqlIntegrationSmokeTest; import org.intellij.lang.annotations.Language; import org.testng.annotations.Test; +import java.sql.SQLException; + import static io.airlift.tpch.TpchTable.ORDERS; import static java.lang.String.format; import static org.testng.Assert.assertEquals; @@ -39,6 +41,154 @@ public class TestOpenGaussIntegrationSmokeTest openGaussServer); } + @Test + @Override + public void testUpdateByOneField() + throws SQLException + { + @Language("SQL") String createTableSql = format("" + + "CREATE TABLE %s.tpch.test_update (\n" + + "id int,\n" + + "name varchar,\n" + + "sex char,\n" + + "age int,\n" + + "score varchar,\n" + + "birthday date,\n" + + "salary double precision\n" + + ")", + getSession().getCatalog().get()); + + assertUpdate(createTableSql); + assertUpdate("INSERT INTO test_update VALUES(1, 'Bob', '1', 24, 'excellent', date'1997-09-28', 40000)", 1); + assertUpdate("INSERT INTO test_update VALUES(2, 'Jack', '1', 25, 'good', date'1996-08-14', 35000)", 1); + assertUpdate("INSERT INTO test_update VALUES(3, 'Rose', '2', 22, 'excellent', date'1999-07-11', 10000)", 1); + + assertQuery("SELECT * FROM test_update where id = 1", "VALUES(1, 'Bob', '1', 24, 'excellent', date'1997-09-28', 40000)"); + assertUpdate("UPDATE test_update SET name = 'Kitty', age = 26, birthday = date'1995-08-16' where id = 1", 1); + assertQuery("SELECT * FROM test_update WHERE id = 1", "VALUES(1, 'Kitty', '1', 26, 'excellent', date'1995-08-16', 40000)"); + + assertQuery("SELECT * FROM test_update WHERE score = 'good'", "VALUES(2, 'Jack', '1', 25, 'good', date'1996-08-14', 35000)"); + assertUpdate("UPDATE test_update SET name = 'Jane', sex = '2' where score = 'good'", 1); + assertQuery("SELECT * FROM test_update WHERE score = 'good'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 35000)"); + + assertQuery("SELECT * FROM test_update WHERE salary = 10000", "VALUES(3, 'Rose', '2', 22, 'excellent', date'1999-07-11', 10000)"); + assertUpdate("UPDATE test_update SET score = 'qualified' where salary = 10000.00 ", 1); + assertQuery("SELECT * FROM test_update WHERE salary = 10000", "VALUES(3, 'Rose', '2', 22, 'qualified', date'1999-07-11', 10000)"); + + assertQuery("SELECT * FROM test_update WHERE age = 22", "VALUES(3, 'Rose', '2', 22, 'qualified', date'1999-07-11', 10000)"); + assertUpdate("UPDATE test_update SET age = age + 1, birthday = date'1998-08-16' where age = 22 ", 1); + assertQuery("SELECT * FROM test_update WHERE age = 23", "VALUES(3, 'Rose', '2', 23, 'qualified', date'1998-08-16', 10000)"); + + assertQuery("SELECT * FROM test_update WHERE birthday = date'1996-08-14'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 35000)"); + assertUpdate("UPDATE test_update SET salary = 36000 where birthday = date'1996-08-14'", 1); + assertQuery("SELECT * FROM test_update WHERE birthday = date'1996-08-14'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 36000)"); + + assertQuery("SELECT * FROM test_update WHERE name = 'Jane'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 36000)"); + assertUpdate("UPDATE test_update SET score = 'bad', birthday = date'1995-10-16' where name = 'Jane'", 1); + assertQuery("SELECT * FROM test_update WHERE name = 'Jane'", "VALUES(2, 'Jane', '2', 25, 'bad', date'1995-10-16', 36000)"); + + assertUpdate("DROP TABLE test_update"); + } + + @Test + @Override + public void testUpdateByMutiField() + throws SQLException + { + @Language("SQL") String createTableSql = format("" + + "CREATE TABLE %s.tpch.test_update (\n" + + "name varchar,\n" + + "id int,\n" + + "score varchar\n" + + ")", + getSession().getCatalog().get()); + + assertUpdate(createTableSql); + assertUpdate("INSERT INTO test_update VALUES('Bob', 1, 'excellent')", 1); + assertUpdate("INSERT INTO test_update VALUES('Tim', 2, 'good')", 1); + assertUpdate("INSERT INTO test_update VALUES('Jane', 3, 'good')", 1); + assertUpdate("INSERT INTO test_update VALUES('Rose', 4, 'bad')", 1); + assertUpdate("INSERT INTO test_update VALUES('Petty', 5, 'good')", 1); + + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + assertQuery("SELECT * FROM test_update WHERE id = 5", "VALUES('Petty', 5, 'good')"); + assertUpdate("UPDATE test_update SET name = 'Kitt' WHERE id IN (4, 5) AND NAME LIKE 'P%'", 1); + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + assertQuery("SELECT * FROM test_update WHERE id = 5", "VALUES('Kitt', 5, 'good')"); + + assertQuery("SELECT * FROM test_update WHERE id = 2", "VALUES('Tim', 2, 'good')"); + assertQuery("SELECT * FROM test_update WHERE id = 3", "VALUES('Jane', 3, 'good')"); + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + assertUpdate("UPDATE test_update SET score = 'excellent' WHERE id BETWEEN 2 And 4 AND score != 'bad'", 2); + assertQuery("SELECT * FROM test_update WHERE id = 2", "VALUES('Tim', 2, 'excellent')"); + assertQuery("SELECT * FROM test_update WHERE id = 3", "VALUES('Jane', 3, 'excellent')"); + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + + assertUpdate("DROP TABLE test_update"); + } + + @Test + @Override + public void testDeleteByOneField() + throws SQLException + { + @Language("SQL") String createTableSql = format("" + + "CREATE TABLE %s.tpch.test_delete (\n" + + "id int,\n" + + "name varchar,\n" + + "sex char,\n" + + "age int,\n" + + "score varchar,\n" + + "birthday date,\n" + + "salary double precision\n" + + ")", + getSession().getCatalog().get()); + assertUpdate(createTableSql); + assertUpdate("INSERT INTO test_delete VALUES(1, 'Bob', '1', 24, 'excellent', date'1997-09-28', 40000)", 1); + assertUpdate("INSERT INTO test_delete VALUES(2, 'Jack', '1', 25, 'good', date'1996-08-14', 35000)", 1); + assertUpdate("INSERT INTO test_delete VALUES(3, 'Jane', '2', 23, 'bad', date'1998-07-25', 15000)", 1); + assertUpdate("INSERT INTO test_delete VALUES(4, 'Rose', '2', 22, 'excellent', date'1999-07-11', 10000)", 1); + + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(4)"); + assertUpdate("DELETE FROM test_delete WHERE name = 'Bob'", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(3)"); + assertUpdate("DELETE FROM test_delete WHERE birthday = date'1996-08-14'", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(2)"); + assertUpdate("DELETE FROM test_delete WHERE salary = 15000", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(1)"); + assertUpdate("DROP TABLE test_delete"); + } + + @Test + @Override + public void testDeleteByMutiField() + throws SQLException + { + @Language("SQL") String createTableSql = format("" + + "CREATE TABLE %s.tpch.test_delete (\n" + + "name varchar,\n" + + "id int,\n" + + "score varchar\n" + + ")", + getSession().getCatalog().get()); + + assertUpdate(createTableSql); + assertUpdate("INSERT INTO test_delete VALUES('Bob', 1, 'excellent')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Tim', 2, 'good')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Jane', 3, 'good')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Rose', 4, 'bad')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Petty', 5, 'good')", 1); + + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(5)"); + assertUpdate("DELETE FROM test_delete WHERE id BETWEEN 2 AND 4 AND name IN ('Rose', 'Petty')", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(4)"); + assertUpdate("DELETE FROM test_delete WHERE name = 'Petty' AND score = 'good'", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(3)"); + assertUpdate("DELETE FROM test_delete WHERE name LIKE '%o%' OR score = 'good'", 3); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(0)"); + assertUpdate("DROP TABLE test_delete"); + } + @Test @Override public void testMaterializedView() diff --git a/presto-postgresql/pom.xml b/presto-postgresql/pom.xml index a7d1462e3..38c3edf65 100644 --- a/presto-postgresql/pom.xml +++ b/presto-postgresql/pom.xml @@ -157,5 +157,10 @@ annotations test + + + io.airlift + log + diff --git a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/BasePostgreSqlClient.java b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/BasePostgreSqlClient.java index a62a9b554..3209f9d25 100644 --- a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/BasePostgreSqlClient.java +++ b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/BasePostgreSqlClient.java @@ -16,9 +16,11 @@ package io.prestosql.plugin.postgresql; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.json.ObjectMapperProvider; +import io.airlift.log.Logger; import io.airlift.slice.DynamicSliceOutput; import io.airlift.slice.Slice; import io.airlift.slice.SliceOutput; @@ -26,8 +28,10 @@ import io.prestosql.plugin.jdbc.BaseJdbcClient; import io.prestosql.plugin.jdbc.BaseJdbcConfig; import io.prestosql.plugin.jdbc.BlockReadFunction; import io.prestosql.plugin.jdbc.BlockWriteFunction; +import io.prestosql.plugin.jdbc.BooleanWriteFunction; import io.prestosql.plugin.jdbc.ColumnMapping; import io.prestosql.plugin.jdbc.ConnectionFactory; +import io.prestosql.plugin.jdbc.DoubleWriteFunction; import io.prestosql.plugin.jdbc.JdbcColumnHandle; import io.prestosql.plugin.jdbc.JdbcIdentity; import io.prestosql.plugin.jdbc.JdbcTableHandle; @@ -35,8 +39,15 @@ import io.prestosql.plugin.jdbc.JdbcTypeHandle; import io.prestosql.plugin.jdbc.LongWriteFunction; import io.prestosql.plugin.jdbc.SliceWriteFunction; import io.prestosql.plugin.jdbc.StatsCollecting; +import io.prestosql.plugin.jdbc.WriteFunction; +import io.prestosql.plugin.jdbc.WriteMapping; +import io.prestosql.plugin.jdbc.WriteNullFunction; +import io.prestosql.plugin.jdbc.optimization.JdbcPushDownModule; import io.prestosql.spi.PrestoException; +import io.prestosql.spi.block.Block; +import io.prestosql.spi.connector.ColumnHandle; import io.prestosql.spi.connector.ConnectorSession; +import io.prestosql.spi.connector.ConnectorTableHandle; import io.prestosql.spi.connector.ConnectorTableMetadata; import io.prestosql.spi.connector.SchemaTableName; import io.prestosql.spi.connector.TableNotFoundException; @@ -59,19 +70,23 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalLong; import java.util.UUID; import java.util.function.BiFunction; import static com.fasterxml.jackson.core.JsonFactory.Feature.CANONICALIZE_FIELD_NAMES; import static com.fasterxml.jackson.databind.SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.google.common.base.Verify.verify; +import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.SizeOf.SIZE_OF_LONG; import static io.airlift.slice.Slices.wrappedLongArray; import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; -import static io.prestosql.plugin.jdbc.StandardColumnMappings.timestampColumnMapping; import static io.prestosql.plugin.postgresql.TypeUtils.getJdbcObjectArray; import static io.prestosql.plugin.postgresql.TypeUtils.jdbcObjectArrayToBlock; import static io.prestosql.plugin.postgresql.TypeUtils.toBoxedArray; @@ -82,13 +97,21 @@ import static io.prestosql.spi.type.DateTimeEncoding.packDateTimeWithZone; import static io.prestosql.spi.type.DateTimeEncoding.unpackMillisUtc; import static io.prestosql.spi.type.TimeZoneKey.UTC_KEY; import static io.prestosql.spi.type.TimestampWithTimeZoneType.TIMESTAMP_WITH_TIME_ZONE; +import static io.prestosql.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.sql.DatabaseMetaData.columnNoNulls; +import static java.util.Locale.ENGLISH; public abstract class BasePostgreSqlClient extends BaseJdbcClient { + private static final Logger log = Logger.get(BasePostgreSqlClient.class); + + /** + * If disabled, do not accept sub-query push down. + */ + private final JdbcPushDownModule pushDownModule; protected static final String DUPLICATE_TABLE_SQLSTATE = "42P07"; protected final Type jsonType; @@ -104,6 +127,7 @@ public abstract class BasePostgreSqlClient super(config, "\"", connectionFactory); this.jsonType = typeManager.getType(new TypeSignature(StandardTypes.JSON)); this.uuidType = typeManager.getType(new TypeSignature(StandardTypes.UUID)); + this.pushDownModule = config.getPushDownModule(); switch (postgresqlConfig.getArrayMapping()) { case DISABLED: @@ -241,56 +265,6 @@ public abstract class BasePostgreSqlClient } } - @Override - public Optional toPrestoType(ConnectorSession session, Connection connection, JdbcTypeHandle typeHandle) - { - String jdbcTypeName = typeHandle.getJdbcTypeName() - .orElseThrow(() -> new PrestoException(JDBC_ERROR, "Type name is missing: " + typeHandle)); - - switch (jdbcTypeName) { - case "uuid": - return Optional.of(uuidColumnMapping()); - case "jsonb": - case "json": - return Optional.of(jsonColumnMapping()); - case "timestamptz": - // PostgreSQL's "timestamp with time zone" is reported as Types.TIMESTAMP rather than Types.TIMESTAMP_WITH_TIMEZONE - return Optional.of(timestampWithTimeZoneColumnMapping()); - default: - break; - } - if (typeHandle.getJdbcType() == Types.VARCHAR && !jdbcTypeName.equals("varchar")) { - // This can be e.g. an ENUM - return Optional.of(typedVarcharColumnMapping(jdbcTypeName)); - } - if (typeHandle.getJdbcType() == Types.TIMESTAMP) { - return Optional.of(timestampColumnMapping()); - } - if (typeHandle.getJdbcType() == Types.ARRAY && supportArrays) { - if (!typeHandle.getArrayDimensions().isPresent()) { - return Optional.empty(); - } - JdbcTypeHandle elementTypeHandle = getArrayElementTypeHandle(connection, typeHandle); - String elementTypeName = typeHandle.getJdbcTypeName() - .orElseThrow(() -> new PrestoException(JDBC_ERROR, "Element type name is missing: " + elementTypeHandle)); - if (elementTypeHandle.getJdbcType() == Types.VARBINARY) { - // PostgreSQL jdbc driver doesn't currently support array of varbinary (bytea[]) - // https://github.com/pgjdbc/pgjdbc/pull/1184 - return Optional.empty(); - } - return toPrestoType(session, connection, elementTypeHandle) - .map(elementMapping -> { - ArrayType prestoArrayType = new ArrayType(elementMapping.getType()); - int arrayDimensions = typeHandle.getArrayDimensions().get(); - for (int i = 1; i < arrayDimensions; i++) { - prestoArrayType = new ArrayType(prestoArrayType); - } - return arrayColumnMapping(session, prestoArrayType, elementTypeName); - }); - } - return super.toPrestoType(session, connection, typeHandle); - } - @Override protected Optional> limitFunction() { @@ -420,4 +394,255 @@ public abstract class BasePostgreSqlClient // so we pass an InputStreamReader instead. return JSON_FACTORY.createParser(new InputStreamReader(json.getInput(), UTF_8)); } + + @Override + public ConnectorTableHandle beginUpdate(ConnectorSession session, ConnectorTableHandle tableHandle, List updatedColumnTypes) + { + JdbcTableHandle jdbcTableHandle = (JdbcTableHandle) tableHandle; + jdbcTableHandle.setUpdatedColumnTypes(updatedColumnTypes); + jdbcTableHandle.setDeleteOrUpdate(true); + return jdbcTableHandle; + } + + private void setStatement(ConnectorSession session, ConnectorTableHandle tableHandle, PreparedStatement statement, Block block, int position, int channel) + throws SQLException + { + JdbcTableHandle jdbcTableHandle = (JdbcTableHandle) tableHandle; + List updatedColumnTypes = jdbcTableHandle.getUpdatedColumnTypes(); + + List writeMappings = updatedColumnTypes.stream() + .map(type -> + { + WriteMapping writeMapping = toWriteMapping(session, type); + WriteFunction writeFunction = writeMapping.getWriteFunction(); + verify( + type.getJavaType() == writeFunction.getJavaType(), + "openLooKeng type %s is not compatible with write function %s accepting %s", + type, + writeFunction, + writeFunction.getJavaType()); + return writeMapping; + }) + .collect(toImmutableList()); + + List columnWriters = writeMappings.stream() + .map(WriteMapping::getWriteFunction) + .collect(toImmutableList()); + + List nullWriters = writeMappings.stream() + .map(WriteMapping::getWriteNullFunction) + .collect(toImmutableList()); + + int parameterIndex = channel + 1; + + if (block.isNull(position)) { + nullWriters.get(channel).setNull(statement, parameterIndex); + return; + } + + Type type = jdbcTableHandle.getUpdatedColumnTypes().get(channel); + Class javaType = type.getJavaType(); + WriteFunction writeFunction = columnWriters.get(channel); + if (javaType == boolean.class) { + ((BooleanWriteFunction) writeFunction).set(statement, parameterIndex, type.getBoolean(block, position)); + } + else if (javaType == long.class) { + ((LongWriteFunction) writeFunction).set(statement, parameterIndex, type.getLong(block, position)); + } + else if (javaType == double.class) { + ((DoubleWriteFunction) writeFunction).set(statement, parameterIndex, type.getDouble(block, position)); + } + else if (javaType == Slice.class) { + ((SliceWriteFunction) writeFunction).set(statement, parameterIndex, type.getSlice(block, position)); + } + else if (javaType == Block.class) { + ((BlockWriteFunction) writeFunction).set(statement, parameterIndex, (Block) type.getObject(block, position)); + } + else { + throw new VerifyException(format("Unexpected type %s with java type %s", type, javaType.getName())); + } + } + + @Override + public void finishUpdate(ConnectorSession session, ConnectorTableHandle tableHandle, Collection fragments) + { + } + + private Map getColumnNameMap(ConnectorSession session, JdbcTableHandle tableHandle) + { + HashMap columnNameMap = new HashMap<>(); // + List columnList = getColumns(session, tableHandle); + + for (JdbcColumnHandle columnHandle : columnList) { + String columnName = columnHandle.getColumnName(); + columnNameMap.put(columnName.toLowerCase(ENGLISH), columnName); + } + + return columnNameMap; + } + + private List getColumnNameFromDataSource(ConnectorSession session, JdbcTableHandle tableHandle, List columns) + { + Map columnNameMap = getColumnNameMap(session, tableHandle); + List updatedColumns = new ArrayList<>(); + + for (String columnName : columns) { + String originName = columnNameMap.get(columnName.toLowerCase(ENGLISH)); + updatedColumns.add((originName != null) ? originName : columnName); + } + + return updatedColumns; + } + + private String buildRemoteSchemaTableName(JdbcTableHandle tableHandle) + { + StringBuilder remoteSchemaTable = new StringBuilder(); + + if (!isNullOrEmpty(tableHandle.getSchemaName())) { + remoteSchemaTable.append(quoted(tableHandle.getSchemaName())).append("."); + } + + remoteSchemaTable.append(quoted(tableHandle.getTableName())); + + return remoteSchemaTable.toString(); + } + + @Override + public String buildUpdateSql(ConnectorSession session, ConnectorTableHandle handle, int setNum, + List updatedColumns) + { + JdbcTableHandle tableHandle = (JdbcTableHandle) handle; + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(format("UPDATE %s SET ", buildRemoteSchemaTableName(tableHandle))); + List columnList = getColumnNameFromDataSource(session, tableHandle, updatedColumns); + for (int i = 0; i < setNum; i++) { + sqlBuilder.append(quoted(columnList.get(i))); + sqlBuilder.append(" = ? "); + if (i != setNum - 1) { + sqlBuilder.append(", "); + } + } + + sqlBuilder.append("WHERE " + "ctid" + "=?::tid"); + return sqlBuilder.toString(); + } + + @Override + public void setUpdateSql(ConnectorSession session, ConnectorTableHandle tableHandle, PreparedStatement statement, List columnValueAndCtidBlock, int position, List updatedColumns) + { + Block ctids = columnValueAndCtidBlock.get(columnValueAndCtidBlock.size() - 1); + + try { + for (int i = 0; i < updatedColumns.size(); i++) { + setStatement(session, tableHandle, statement, columnValueAndCtidBlock.get(i), position, i); + } + String ctid = ctids.getString(position, position, 10); + statement.setString(updatedColumns.size() + 1, ctid); + } + catch (SQLException e) { + throw new PrestoException(JDBC_ERROR, e); + } + } + + @Override + public ColumnHandle getUpdateRowIdColumnHandle(ConnectorSession session, ConnectorTableHandle tableHandle, List updatedColumns) + { + JdbcTypeHandle jdbcTypeHandle = new JdbcTypeHandle(Types.CHAR, Optional.of("char"), 10, 0, Optional.empty()); + return new JdbcColumnHandle("ctid", jdbcTypeHandle, VARCHAR, true); + } + + @Override + public ColumnHandle getDeleteRowIdColumnHandle(ConnectorSession session, ConnectorTableHandle tableHandle) + { + JdbcTypeHandle jdbcTypeHandle = new JdbcTypeHandle(Types.CHAR, Optional.of("char"), 10, 0, Optional.empty()); + return new JdbcColumnHandle("ctid", jdbcTypeHandle, VARCHAR, true); + } + + @Override + public Optional applyDelete(ConnectorSession session, ConnectorTableHandle handle) + { + if (pushDownModule.equals(JdbcPushDownModule.DEFAULT)) { + return Optional.empty(); + } + return Optional.of(handle); + } + + private String extractSubQuery(String subQuery) + { + String query = subQuery.substring(subQuery.indexOf("WHERE")); + int count = 1; + int lastIndex = 0; + for (int i = query.indexOf("(") + 1; i < query.length(); i++) { + if (String.valueOf(query.charAt(i)).equals("(")) { + count++; + } + if (String.valueOf(query.charAt(i)).equals(")")) { + count--; + } + if (count == 0) { + lastIndex = i; + break; + } + } + return query.substring(0, lastIndex + 1); + } + + @Override + public OptionalLong executeDelete(ConnectorSession session, ConnectorTableHandle handle) + { + JdbcIdentity identity = JdbcIdentity.from(session); + JdbcTableHandle basePostgresqlHandle = (JdbcTableHandle) handle; + try (Connection connection = connectionFactory.openConnection(identity)) { + String sql = "DELETE FROM " + basePostgresqlHandle.getSchemaPrefixedTableName(); + if (basePostgresqlHandle.getGeneratedSql().isPresent()) { + String subQuery = basePostgresqlHandle.getGeneratedSql().get().getSql(); + sql = String.format("%s %s", sql, extractSubQuery(subQuery)); + try (PreparedStatement statement = connection.prepareStatement(sql)) { + return OptionalLong.of(statement.executeUpdate()); + } + } + else { + try (PreparedStatement statement = connection.prepareStatement(sql)) { + log.debug("Execute: %s", sql); + return OptionalLong.of(statement.executeUpdate()); + } + } + } + catch (SQLException e) { + throw new PrestoException(JDBC_ERROR, e); + } + } + + @Override + public ConnectorTableHandle beginDelete(ConnectorSession session, ConnectorTableHandle tableHandle) + { + JdbcTableHandle jdbcTableHandle = (JdbcTableHandle) tableHandle; + jdbcTableHandle.setDeleteOrUpdate(true); + return jdbcTableHandle; + } + + @Override + public void finishDelete(ConnectorSession session, ConnectorTableHandle tableHandle, Collection fragments) + { + } + + @Override + public String buildDeleteSql(ConnectorTableHandle handle) + { + JdbcTableHandle tableHandle = (JdbcTableHandle) handle; + return format( + "DELETE FROM %s WHERE ctid=%s", buildRemoteSchemaTableName(tableHandle), "?::tid"); + } + + @Override + public void setDeleteSql(PreparedStatement statement, Block ctids, int position) + { + String ctid = ctids.getString(position, position, 10); + try { + statement.setString(1, ctid); + } + catch (SQLException e) { + throw new PrestoException(JDBC_ERROR, e); + } + } } diff --git a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java index c4788097f..b621dd29b 100644 --- a/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java +++ b/presto-postgresql/src/main/java/io/prestosql/plugin/postgresql/PostgreSqlClient.java @@ -16,8 +16,12 @@ package io.prestosql.plugin.postgresql; import io.prestosql.plugin.jdbc.BaseJdbcConfig; import io.prestosql.plugin.jdbc.ColumnMapping; import io.prestosql.plugin.jdbc.ConnectionFactory; +import io.prestosql.plugin.jdbc.JdbcIdentity; +import io.prestosql.plugin.jdbc.JdbcSplit; +import io.prestosql.plugin.jdbc.JdbcTypeHandle; import io.prestosql.plugin.jdbc.SliceWriteFunction; import io.prestosql.plugin.jdbc.WriteMapping; +import io.prestosql.spi.PrestoException; import io.prestosql.spi.connector.ConnectorSession; import io.prestosql.spi.type.ArrayType; import io.prestosql.spi.type.StandardTypes; @@ -29,8 +33,15 @@ import org.postgresql.util.PGobject; import javax.inject.Inject; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Optional; + import static io.airlift.slice.Slices.utf8Slice; import static io.prestosql.plugin.jdbc.ColumnMapping.DISABLE_PUSHDOWN; +import static io.prestosql.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; +import static io.prestosql.plugin.jdbc.StandardColumnMappings.timestampColumnMapping; import static io.prestosql.plugin.jdbc.StandardColumnMappings.timestampWriteFunction; import static io.prestosql.plugin.jdbc.StandardColumnMappings.tinyintWriteFunction; import static io.prestosql.plugin.jdbc.StandardColumnMappings.varbinaryWriteFunction; @@ -49,6 +60,64 @@ public class PostgreSqlClient super(config, postgresqlConfig, connectionFactory, typeManager); } + @Override + public Connection getConnection(JdbcIdentity identity, JdbcSplit split) + throws SQLException + { + Connection connection = connectionFactory.openConnection(identity); + return connection; + } + + @Override + public Optional toPrestoType(ConnectorSession session, Connection connection, JdbcTypeHandle typeHandle) + { + String jdbcTypeName = typeHandle.getJdbcTypeName() + .orElseThrow(() -> new PrestoException(JDBC_ERROR, "Type name is missing: " + typeHandle)); + + switch (jdbcTypeName) { + case "uuid": + return Optional.of(uuidColumnMapping()); + case "jsonb": + case "json": + return Optional.of(jsonColumnMapping()); + case "timestamptz": + // PostgreSQL's "timestamp with time zone" is reported as Types.TIMESTAMP rather than Types.TIMESTAMP_WITH_TIMEZONE + return Optional.of(timestampWithTimeZoneColumnMapping()); + default: + break; + } + if (typeHandle.getJdbcType() == Types.VARCHAR && !jdbcTypeName.equals("varchar")) { + // This can be e.g. an ENUM + return Optional.of(typedVarcharColumnMapping(jdbcTypeName)); + } + if (typeHandle.getJdbcType() == Types.TIMESTAMP) { + return Optional.of(timestampColumnMapping()); + } + if (typeHandle.getJdbcType() == Types.ARRAY && supportArrays) { + if (!typeHandle.getArrayDimensions().isPresent()) { + return Optional.empty(); + } + JdbcTypeHandle elementTypeHandle = getArrayElementTypeHandle(connection, typeHandle); + String elementTypeName = typeHandle.getJdbcTypeName() + .orElseThrow(() -> new PrestoException(JDBC_ERROR, "Element type name is missing: " + elementTypeHandle)); + if (elementTypeHandle.getJdbcType() == Types.VARBINARY) { + // PostgreSQL jdbc driver doesn't currently support array of varbinary (bytea[]) + // https://github.com/pgjdbc/pgjdbc/pull/1184 + return Optional.empty(); + } + return toPrestoType(session, connection, elementTypeHandle) + .map(elementMapping -> { + ArrayType prestoArrayType = new ArrayType(elementMapping.getType()); + int arrayDimensions = typeHandle.getArrayDimensions().get(); + for (int i = 1; i < arrayDimensions; i++) { + prestoArrayType = new ArrayType(prestoArrayType); + } + return arrayColumnMapping(session, prestoArrayType, elementTypeName); + }); + } + return super.toPrestoType(session, connection, typeHandle); + } + @Override public WriteMapping toWriteMapping(ConnectorSession session, Type type) { diff --git a/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlIntegrationSmokeTest.java b/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlIntegrationSmokeTest.java index 32f4a1fc7..cad81de39 100644 --- a/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlIntegrationSmokeTest.java +++ b/presto-postgresql/src/test/java/io/prestosql/plugin/postgresql/TestPostgreSqlIntegrationSmokeTest.java @@ -84,6 +84,111 @@ public class TestPostgreSqlIntegrationSmokeTest assertFalse(getQueryRunner().tableExists(getSession(), "test_drop")); } + @Test + public void testUpdateByOneField() + throws SQLException + { + execute("CREATE TABLE tpch.test_update (id serial primary key, name varchar, sex char, age int, score varchar, birthday date, salary double precision)"); + assertUpdate("INSERT INTO test_update VALUES(1, 'Bob', '1', 24, 'excellent', date'1997-09-28', 40000)", 1); + assertUpdate("INSERT INTO test_update VALUES(2, 'Jack', '1', 25, 'good', date'1996-08-14', 35000)", 1); + assertUpdate("INSERT INTO test_update VALUES(3, 'Rose', '2', 22, 'excellent', date'1999-07-11', 10000)", 1); + + assertQuery("SELECT * FROM test_update where id = 1", "VALUES(1, 'Bob', '1', 24, 'excellent', date'1997-09-28', 40000)"); + assertUpdate("UPDATE test_update SET name = 'Kitty', age = 26, birthday = date'1995-08-16' where id = 1", 1); + assertQuery("SELECT * FROM test_update WHERE id = 1", "VALUES(1, 'Kitty', '1', 26, 'excellent', date'1995-08-16', 40000)"); + + assertQuery("SELECT * FROM test_update WHERE score = 'good'", "VALUES(2, 'Jack', '1', 25, 'good', date'1996-08-14', 35000)"); + assertUpdate("UPDATE test_update SET name = 'Jane', sex = '2' where score = 'good'", 1); + assertQuery("SELECT * FROM test_update WHERE score = 'good'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 35000)"); + + assertQuery("SELECT * FROM test_update WHERE salary = 10000", "VALUES(3, 'Rose', '2', 22, 'excellent', date'1999-07-11', 10000)"); + assertUpdate("UPDATE test_update SET score = 'qualified' where salary = 10000.00 ", 1); + assertQuery("SELECT * FROM test_update WHERE salary = 10000", "VALUES(3, 'Rose', '2', 22, 'qualified', date'1999-07-11', 10000)"); + + assertQuery("SELECT * FROM test_update WHERE age = 22", "VALUES(3, 'Rose', '2', 22, 'qualified', date'1999-07-11', 10000)"); + assertUpdate("UPDATE test_update SET age = age + 1, birthday = date'1998-08-16' where age = 22 ", 1); + assertQuery("SELECT * FROM test_update WHERE age = 23", "VALUES(3, 'Rose', '2', 23, 'qualified', date'1998-08-16', 10000)"); + + assertQuery("SELECT * FROM test_update WHERE birthday = date'1996-08-14'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 35000)"); + assertUpdate("UPDATE test_update SET salary = 36000 where birthday = date'1996-08-14'", 1); + assertQuery("SELECT * FROM test_update WHERE birthday = date'1996-08-14'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 36000)"); + + assertQuery("SELECT * FROM test_update WHERE name = 'Jane'", "VALUES(2, 'Jane', '2', 25, 'good', date'1996-08-14', 36000)"); + assertUpdate("UPDATE test_update SET score = 'bad', birthday = date'1995-10-16' where name = 'Jane'", 1); + assertQuery("SELECT * FROM test_update WHERE name = 'Jane'", "VALUES(2, 'Jane', '2', 25, 'bad', date'1995-10-16', 36000)"); + + assertUpdate("DROP TABLE test_update"); + } + + @Test + public void testUpdateByMutiField() + throws SQLException + { + execute("CREATE TABLE tpch.test_update (name varchar, id int, score varchar)"); + assertUpdate("INSERT INTO test_update VALUES('Bob', 1, 'excellent')", 1); + assertUpdate("INSERT INTO test_update VALUES('Tim', 2, 'good')", 1); + assertUpdate("INSERT INTO test_update VALUES('Jane', 3, 'good')", 1); + assertUpdate("INSERT INTO test_update VALUES('Rose', 4, 'bad')", 1); + assertUpdate("INSERT INTO test_update VALUES('Petty', 5, 'good')", 1); + + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + assertQuery("SELECT * FROM test_update WHERE id = 5", "VALUES('Petty', 5, 'good')"); + assertUpdate("UPDATE test_update SET name = 'Kitt' WHERE id IN (4, 5) AND NAME LIKE 'P%'", 1); + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + assertQuery("SELECT * FROM test_update WHERE id = 5", "VALUES('Kitt', 5, 'good')"); + + assertQuery("SELECT * FROM test_update WHERE id = 2", "VALUES('Tim', 2, 'good')"); + assertQuery("SELECT * FROM test_update WHERE id = 3", "VALUES('Jane', 3, 'good')"); + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + assertUpdate("UPDATE test_update SET score = 'excellent' WHERE id BETWEEN 2 And 4 AND score != 'bad'", 2); + assertQuery("SELECT * FROM test_update WHERE id = 2", "VALUES('Tim', 2, 'excellent')"); + assertQuery("SELECT * FROM test_update WHERE id = 3", "VALUES('Jane', 3, 'excellent')"); + assertQuery("SELECT * FROM test_update WHERE id = 4", "VALUES('Rose', 4, 'bad')"); + + assertUpdate("DROP TABLE test_update"); + } + + @Test + public void testDeleteByOneField() + throws SQLException + { + execute("CREATE TABLE tpch.test_delete (id serial primary key, name varchar, sex char, age int, score varchar, birthday date, salary double precision)"); + assertUpdate("INSERT INTO test_delete VALUES(1, 'Bob', '1', 24, 'excellent', date'1997-09-28', 40000)", 1); + assertUpdate("INSERT INTO test_delete VALUES(2, 'Jack', '1', 25, 'good', date'1996-08-14', 35000)", 1); + assertUpdate("INSERT INTO test_delete VALUES(3, 'Jane', '2', 23, 'bad', date'1998-07-25', 15000)", 1); + assertUpdate("INSERT INTO test_delete VALUES(4, 'Rose', '2', 22, 'excellent', date'1999-07-11', 10000)", 1); + + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(4)"); + assertUpdate("DELETE FROM test_delete WHERE name = 'Bob'", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(3)"); + assertUpdate("DELETE FROM test_delete WHERE birthday = date'1996-08-14'", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(2)"); + assertUpdate("DELETE FROM test_delete WHERE salary = 15000", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(1)"); + assertUpdate("DROP TABLE test_delete"); + } + + @Test + public void testDeleteByMutiField() + throws SQLException + { + execute("CREATE TABLE tpch.test_delete (name varchar, id int, score varchar)"); + assertUpdate("INSERT INTO test_delete VALUES('Bob', 1, 'excellent')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Tim', 2, 'good')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Jane', 3, 'good')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Rose', 4, 'bad')", 1); + assertUpdate("INSERT INTO test_delete VALUES('Petty', 5, 'good')", 1); + + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(5)"); + assertUpdate("DELETE FROM test_delete WHERE id BETWEEN 2 AND 4 AND name IN ('Rose', 'Petty')", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(4)"); + assertUpdate("DELETE FROM test_delete WHERE name = 'Petty' AND score = 'good'", 1); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(3)"); + assertUpdate("DELETE FROM test_delete WHERE name LIKE '%o%' OR score = 'good'", 3); + assertQuery("SELECT COUNT() FROM test_delete", "VALUES(0)"); + assertUpdate("DROP TABLE test_delete"); + } + @Test public void testInsert() throws Exception