diff --git a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgWithTableOid.java b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgWithTableOid.java new file mode 100644 index 000000000000..49dbf7133b6f --- /dev/null +++ b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestPgWithTableOid.java @@ -0,0 +1,253 @@ +// Copyright (c) YugaByte, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations +// under the License. +// + +package org.yb.pgsql; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yb.util.YBTestRunnerNonTsanOnly; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.yb.AssertionWrappers.*; + +@RunWith(value=YBTestRunnerNonTsanOnly.class) +public class TestPgWithTableOid extends BasePgSQLTest { + private static final Logger LOG = LoggerFactory.getLogger(TestPgWithTableOid.class); + + // This test is for checking the behaviour when reusing oids in pg_constraint for new tables. + @Test + public void testConflictingOidsWithPgConstraint() throws Exception { + try (Statement statement = connection.createStatement()) { + statement.execute("set yb_enable_create_with_table_oid=1"); + statement.execute("CREATE TABLE test_table(id int, val int," + + "CONSTRAINT tt_id_pkey PRIMARY KEY(id)," + + "CONSTRAINT tt_val_unq UNIQUE(val))"); + statement.execute("INSERT INTO test_table(id, val) VALUES (1,1), (2,2), (3,3)"); + List expectedRows = Arrays.asList(new Row(1,1), new Row(2,2), new Row(3,3)); + + // Get oids that were created. + long table_oid; + long constraint1_oid; + long constraint2_oid; + String query = "SELECT oid FROM pg_class WHERE relname = 'test_table'"; + try (ResultSet rs = statement.executeQuery(query)) { + table_oid = getSingleRow(rs).getLong(0); + } + query = "SELECT oid FROM pg_constraint WHERE conname = 'tt_id_pkey'"; + try (ResultSet rs = statement.executeQuery(query)) { + constraint1_oid = getSingleRow(rs).getLong(0); + } + query = "SELECT oid FROM pg_constraint WHERE conname = 'tt_val_unq'"; + try (ResultSet rs = statement.executeQuery(query)) { + constraint2_oid = getSingleRow(rs).getLong(0); + } + + // Check that creating a table with the same table_oid fails. + runInvalidQuery(statement, + "CREATE TABLE test_table2(id int, val int," + + "CONSTRAINT tt_id_pkey2 PRIMARY KEY(id)," + + "CONSTRAINT tt_val_unq2 UNIQUE(val))" + + "WITH (table_oid = " + table_oid + ")", + "Oid " + table_oid + " is in use."); + + // Try creating new tables with the oids of the constraints. + statement.execute("CREATE TABLE test_table2(id int, val int," + + "CONSTRAINT tt_id_pkey2 PRIMARY KEY(id)," + + "CONSTRAINT tt_val_unq2 UNIQUE(val)) " + + "WITH (table_oid = " + constraint1_oid + ")"); + statement.execute("INSERT INTO test_table2(id, val) VALUES (1,1), (2,2), (3,3)"); + + statement.execute("CREATE TABLE test_table3(id int, val int," + + "CONSTRAINT tt_id_pkey3 PRIMARY KEY(id)," + + "CONSTRAINT tt_val_unq3 UNIQUE(val)) " + + "WITH (table_oid = " + constraint2_oid + ")"); + statement.execute("INSERT INTO test_table3(id, val) VALUES (1,1), (2,2), (3,3)"); + + // Check rows. + try (ResultSet rs = statement.executeQuery("SELECT * FROM test_table ORDER BY id")) { + assertEquals(expectedRows, getRowList(rs)); + } + + try (ResultSet rs = statement.executeQuery("SELECT * FROM test_table2 ORDER BY id")) { + assertEquals(expectedRows, getRowList(rs)); + } + + try (ResultSet rs = statement.executeQuery("SELECT * FROM test_table3 ORDER BY val")) { + assertEquals(expectedRows, getRowList(rs)); + } + } + } + + // This test is for checking the behaviour when using oids that would be + // selected next in pg_class for new tables. + @Test + public void testConflictingOidsWithPgClass() throws Exception { + try (Statement statement = connection.createStatement()) { + statement.execute("set yb_enable_create_with_table_oid=1"); + // First create a simple table and get its oid. + statement.execute("CREATE TABLE test_table1(a int)"); + long table1_oid; + String query = "SELECT oid FROM pg_class WHERE relname = 'test_table1'"; + try (ResultSet rs = statement.executeQuery(query)) { + table1_oid = getSingleRow(rs).getLong(0); + } + + // A simple table will generate 3 new oids: table1_oid, table1_oid + 1, table1_oid + 2 + // (these additional oids are used in pg_type) + // We will next generate a new table with table_oid = table1_oid + 5 + // This will also generate two new oids: table1_oid + 3, table1_oid + 4 + long table2_oid = table1_oid + 5; + statement.execute("CREATE TABLE test_table2(a int) WITH (table_oid = " + table2_oid + ")"); + query = "SELECT oid FROM pg_class WHERE relname = 'test_table2'"; + try (ResultSet rs = statement.executeQuery(query)) { + assertEquals(table2_oid, getSingleRow(rs).getLong(0).longValue()); + } + + // Ensure that pg_type has the 4 expected oids. + query = "SELECT COUNT(*) FROM pg_type WHERE " + + "oid = " + (table1_oid + 1) + " or " + + "oid = " + (table1_oid + 2) + " or " + + "oid = " + (table1_oid + 3) + " or " + + "oid = " + (table1_oid + 4); + try (ResultSet rs = statement.executeQuery(query)) { + assertEquals(4, getSingleRow(rs).getLong(0).longValue()); + } + + // Now generate a normal table. It should try to use the next generated oid (table1_oid + 5), + // however since that value is in use by test_table2, it should end up having + // oid = table1_oid + 6. + statement.execute("CREATE TABLE test_table3(a int)"); + query = "SELECT oid FROM pg_class WHERE relname = 'test_table3'"; + try (ResultSet rs = statement.executeQuery(query)) { + assertEquals(table1_oid + 6, getSingleRow(rs).getLong(0).longValue()); + } + } + } + + private void createDatabaseObjects(Connection cxn, int idx) throws Exception { + try (Statement stmt = cxn.createStatement()) { + stmt.execute("set yb_enable_create_with_table_oid=1"); + // Execute a simplest SQL statement. + stmt.execute("SELECT 1"); + int tableOid = 33333; + int indexOid = 44444; + + // Create simple table. + String tableName = "test_createwithtableoid"; + String sql = + String.format("CREATE TABLE %s(h bigint, r float, vi int, vs text, PRIMARY KEY (h, r))" + + "WITH (table_oid = %d)", tableName, tableOid); + stmt.execute(sql); + + // Create index. + sql = String.format("CREATE INDEX %s_rindex on %s(r) WITH (table_oid = %d)", + tableName, tableName, indexOid); + stmt.execute(sql); + + // Insert some data, data will vary depending on idx. + for (int i = 0; i < 100 * idx; i += 1 * idx) { + sql = String.format("INSERT INTO %s VALUES (%d, %f, %d, 'value_%d')", + tableName, i, 1.5*i, 2*i, i); + stmt.execute(sql); + } + } + } + + private void validateDatabaseObjects(Connection cxn, int idx) throws Exception { + try (Statement stmt = cxn.createStatement()) { + String tableName = "test_createwithtableoid"; + int tableOid = 33333; + int indexOid = 44444; + + // Check table_oids; + String query = "SELECT oid FROM pg_class WHERE relname = '" + tableName + "'"; + try (ResultSet rs = stmt.executeQuery(query)) { + assertEquals(tableOid, getSingleRow(rs).getLong(0).longValue()); + } + query = "SELECT oid FROM pg_class WHERE relname = '" + tableName + "_rindex'"; + try (ResultSet rs = stmt.executeQuery(query)) { + assertEquals(indexOid, getSingleRow(rs).getLong(0).longValue()); + } + + // Test table contents. + final String PRIMARY_KEY = "test_createwithtableoid_pkey"; + List allRows = new ArrayList<>(); + for (int i = 0; i < 100 * idx; i += 1 * idx) { + allRows.add(new Row((long) i, + 1.5*i, + 2*i, + "value_" + i)); + } + + query = "SELECT * FROM " + tableName; + try (ResultSet rs = stmt.executeQuery(query)) { + assertEquals(allRows, getSortedRowList(rs)); + } + assertFalse(isIndexScan(stmt, query, PRIMARY_KEY)); + + query = "SELECT * FROM " + tableName + " WHERE h = " + idx; + try (ResultSet rs = stmt.executeQuery(query)) { + List expectedRows = allRows.stream() + .filter(row -> row.getLong(0).equals((long) idx)) + .collect(Collectors.toList()); + assertEquals(1, expectedRows.size()); + assertEquals(expectedRows, getSortedRowList(rs)); + } + assertTrue(isIndexScan(stmt, query, PRIMARY_KEY)); + } + } + + // This test is for checking the behaviour when using the same table_oids for + // tables in different databases (This is fine since each database has its + // own pg_class table). + @Test + public void testConflictingTableOidsAcrossDatabases() throws Exception { + String dbnameroot = "testwithtableoids_db"; + final int kNumLoops = 3; + + // Create databases and objects. + for (int i = 0; i < kNumLoops; i++) { + String dbname = dbnameroot + i; + try (Connection connection0 = getConnectionBuilder().withTServer(0).connect(); + Statement statement = connection0.createStatement()) { + statement.execute(String.format("CREATE DATABASE %s", dbname)); + + // Creating a few objects in the database. + try (Connection connection1 = getConnectionBuilder().withTServer(1) + .withDatabase(dbname) + .connect()) { + createDatabaseObjects(connection1, i + 1); + } + } + } + + // Validate that everything works fine. + for (int i = 0; i < kNumLoops; i++) { + String dbname = dbnameroot + i; + try (Connection connection0 = getConnectionBuilder().withTServer(1) + .withDatabase(dbname) + .connect()) { + validateDatabaseObjects(connection0, i + 1); + } + } + } +} diff --git a/src/postgres/src/backend/access/common/reloptions.c b/src/postgres/src/backend/access/common/reloptions.c index c58ffc721318..b0ab38e73da6 100644 --- a/src/postgres/src/backend/access/common/reloptions.c +++ b/src/postgres/src/backend/access/common/reloptions.c @@ -23,6 +23,7 @@ #include "access/nbtree.h" #include "access/reloptions.h" #include "access/spgist.h" +#include "access/transam.h" #include "access/tuptoaster.h" #include "catalog/pg_type.h" #include "commands/defrem.h" @@ -364,11 +365,20 @@ static relopt_int intRelOpts[] = { "tablegroup", "Tablegroup oid for this relation.", - RELOPT_KIND_HEAP, + RELOPT_KIND_HEAP | RELOPT_KIND_INDEX, AccessExclusiveLock }, -1, 0, INT_MAX }, + { + { + "table_oid", + "Postgres table oid for this relation.", + RELOPT_KIND_HEAP | RELOPT_KIND_INDEX, + AccessExclusiveLock + }, + -1, FirstNormalObjectId, INT_MAX + }, /* list terminator */ {{NULL}} }; @@ -1406,6 +1416,7 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) {"colocated", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, colocated)}, {"tablegroup", RELOPT_TYPE_INT, offsetof(StdRdOptions, tablegroup)}, + {"table_oid", RELOPT_TYPE_INT, offsetof(StdRdOptions, table_oid)}, }; options = parseRelOptions(reloptions, validate, kind, &numoptions); diff --git a/src/postgres/src/backend/access/ybc/ybcin.c b/src/postgres/src/backend/access/ybc/ybcin.c index 474d2a1cd7a4..ead3105b86d4 100644 --- a/src/postgres/src/backend/access/ybc/ybcin.c +++ b/src/postgres/src/backend/access/ybc/ybcin.c @@ -26,6 +26,7 @@ #include "miscadmin.h" #include "access/nbtree.h" +#include "access/reloptions.h" #include "access/relscan.h" #include "access/sysattr.h" #include "access/ybcam.h" @@ -237,6 +238,13 @@ ybcincostestimate(struct PlannerInfo *root, struct IndexPath *path, double loop_ bytea * ybcinoptions(Datum reloptions, bool validate) { + /* + * For now we only need to validate the reloptions, as we currently have no + * need for a special struct similar to BrinOptions or GinOptions. + * Thus, we will still return NULL for now. + */ + int numoptions; + (void) parseRelOptions(reloptions, validate, RELOPT_KIND_INDEX, &numoptions); return NULL; } diff --git a/src/postgres/src/backend/catalog/catalog.c b/src/postgres/src/backend/catalog/catalog.c index 4c7919cef74d..db566b0b6dff 100644 --- a/src/postgres/src/backend/catalog/catalog.c +++ b/src/postgres/src/backend/catalog/catalog.c @@ -21,6 +21,7 @@ #include #include "access/genam.h" +#include "access/heapam.h" #include "access/sysattr.h" #include "access/transam.h" #include "catalog/catalog.h" @@ -41,6 +42,7 @@ #include "catalog/pg_type.h" #include "catalog/pg_yb_catalog_version.h" #include "catalog/toasting.h" +#include "commands/defrem.h" #include "miscadmin.h" #include "storage/fd.h" #include "utils/fmgroids.h" @@ -267,6 +269,93 @@ IsSharedRelation(Oid relationId) return false; } +/* + * GetBackendOidFromRelPersistence + * Returns backend oid for the given type of relation persistence. + */ +Oid +GetBackendOidFromRelPersistence(char relpersistence) +{ + switch (relpersistence) + { + case RELPERSISTENCE_TEMP: + return BackendIdForTempRelations(); + case RELPERSISTENCE_UNLOGGED: + case RELPERSISTENCE_PERMANENT: + return InvalidBackendId; + default: + elog(ERROR, "invalid relpersistence: %c", relpersistence); + return InvalidOid; /* placate compiler */ + } +} + +/* + * DoesRelFileExist + * True iff there is an existing file of the same name for this relation. + */ +bool +DoesRelFileExist(const RelFileNodeBackend *rnode) +{ + bool collides; + char *rpath = relpath(*rnode, MAIN_FORKNUM); + int fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY); + + if (fd >= 0) + { + /* definite collision */ + close(fd); + collides = true; + } + else + { + /* + * Here we have a little bit of a dilemma: if errno is something + * other than ENOENT, should we declare a collision and loop? In + * particular one might think this advisable for, say, EPERM. + * However there really shouldn't be any unreadable files in a + * tablespace directory, and if the EPERM is actually complaining + * that we can't read the directory itself, we'd be in an infinite + * loop. In practice it seems best to go ahead regardless of the + * errno. If there is a colliding file we will get an smgr + * failure when we attempt to create the new relation file. + */ + collides = false; + } + + pfree(rpath); + return collides; +} + +/* + * DoesOidExistInRelation + * True iff the oid already exists in the relation. + * Used typically with relation = pg_class, to check if a new oid is + * already in use. + */ +bool +DoesOidExistInRelation(Oid oid, + Relation relation, + Oid indexId, + AttrNumber oidcolumn) +{ + SysScanDesc scan; + ScanKeyData key; + bool collides; + + ScanKeyInit(&key, + oidcolumn, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(oid)); + + /* see notes in GetNewOid about using SnapshotAny */ + scan = systable_beginscan(relation, indexId, true, SnapshotAny, 1, &key); + + collides = HeapTupleIsValid(systable_getnext(scan)); + + systable_endscan(scan); + + return collides; +} /* * GetNewOid @@ -350,9 +439,6 @@ Oid GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) { Oid newOid; - SysScanDesc scan; - ScanKeyData key; - bool collides; /* * We should never be asked to generate a new pg_type OID during @@ -368,20 +454,7 @@ GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn) CHECK_FOR_INTERRUPTS(); newOid = GetNewObjectId(); - - ScanKeyInit(&key, - oidcolumn, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(newOid)); - - /* see notes above about using SnapshotAny */ - scan = systable_beginscan(relation, indexId, true, - SnapshotAny, 1, &key); - - collides = HeapTupleIsValid(systable_getnext(scan)); - - systable_endscan(scan); - } while (collides); + } while (DoesOidExistInRelation(newOid, relation, indexId, oidcolumn)); return newOid; } @@ -406,10 +479,6 @@ Oid GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) { RelFileNodeBackend rnode; - char *rpath; - int fd; - bool collides; - BackendId backend; /* * If we ever get here during pg_upgrade, there's something wrong; all @@ -418,20 +487,6 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) */ Assert(!IsBinaryUpgrade); - switch (relpersistence) - { - case RELPERSISTENCE_TEMP: - backend = BackendIdForTempRelations(); - break; - case RELPERSISTENCE_UNLOGGED: - case RELPERSISTENCE_PERMANENT: - backend = InvalidBackendId; - break; - default: - elog(ERROR, "invalid relpersistence: %c", relpersistence); - return InvalidOid; /* placate compiler */ - } - /* This logic should match RelationInitPhysicalAddr */ rnode.node.spcNode = reltablespace ? reltablespace : MyDatabaseTableSpace; rnode.node.dbNode = (rnode.node.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : MyDatabaseId; @@ -441,7 +496,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) * that properly here to make sure that any collisions based on filename * are properly detected. */ - rnode.backend = backend; + rnode.backend = GetBackendOidFromRelPersistence(relpersistence);; do { @@ -454,33 +509,120 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) rnode.node.relNode = GetNewObjectId(); /* Check for existing file of same name */ - rpath = relpath(rnode, MAIN_FORKNUM); - fd = BasicOpenFile(rpath, O_RDONLY | PG_BINARY); + } while (DoesRelFileExist(&rnode)); - if (fd >= 0) - { - /* definite collision */ - close(fd); - collides = true; - } - else + return rnode.node.relNode; +} + +/* + * IsTableOidUnused + * Returns true iff the given table oid is not used by any other table + * within the database of the given tablespace. + * + * First checks pg_class to see if the oid is in use (similar to + * GetNewOidWithIndex), and then checks if there are any existing relfiles that + * have the same oid (similar to GetNewRelFileNode). + * + * Similar to GetNewOidWithIndex and GetNewRelFileNode, there is a theoretical + * race condition, but since we don't worry about it there, it should be fine + * here as well. + */ +bool +IsTableOidUnused(Oid table_oid, + Oid reltablespace, + Relation pg_class, + char relpersistence) +{ + RelFileNodeBackend rnode; + Oid oidIndex; + bool collides; + + /* First check for if the oid is used in pg_class. */ + + /* The relcache will cache the identity of the OID index for us */ + oidIndex = RelationGetOidIndex(pg_class); + + if (!OidIsValid(oidIndex)) + { + elog(WARNING, "Could not find oid index of pg_class."); + } + + collides = DoesOidExistInRelation(table_oid, + pg_class, + oidIndex, + ObjectIdAttributeNumber); + + if (!collides) + { + /* + * Check if there are existing relfiles with the oid. + * YB Note: It looks like we only run into collisions here for + * temporary tables. + */ + + /* + * The relpath will vary based on the backend ID, so we must initialize + * that properly here to make sure that any collisions based on filename + * are properly detected. + */ + rnode.backend = GetBackendOidFromRelPersistence(relpersistence); + + /* This logic should match RelationInitPhysicalAddr */ + rnode.node.spcNode = reltablespace ? reltablespace + : MyDatabaseTableSpace; + rnode.node.dbNode = (rnode.node.spcNode == GLOBALTABLESPACE_OID) + ? InvalidOid + : MyDatabaseId; + + rnode.node.relNode = table_oid; + + /* Check for existing file of same name */ + collides = DoesRelFileExist(&rnode); + } + + return !collides; +} + +/* + * GetTableOidFromRelOptions + * Scans through relOptions for any 'table_oid' options, and checks if + * that oid is available. If so, return that oid, else return InvalidOid. + */ +Oid +GetTableOidFromRelOptions(List *relOptions, + Oid reltablespace, + char relpersistence) +{ + ListCell *opt_cell; + Oid table_oid; + bool is_oid_free; + + foreach(opt_cell, relOptions) + { + DefElem *def = (DefElem *) lfirst(opt_cell); + if (strcmp(def->defname, "table_oid") == 0) { - /* - * Here we have a little bit of a dilemma: if errno is something - * other than ENOENT, should we declare a collision and loop? In - * particular one might think this advisable for, say, EPERM. - * However there really shouldn't be any unreadable files in a - * tablespace directory, and if the EPERM is actually complaining - * that we can't read the directory itself, we'd be in an infinite - * loop. In practice it seems best to go ahead regardless of the - * errno. If there is a colliding file we will get an smgr - * failure when we attempt to create the new relation file. - */ - collides = false; + table_oid = defGetInt32(def); + if (OidIsValid(table_oid)) + { + Relation pg_class_desc = + heap_open(RelationRelationId, RowExclusiveLock); + is_oid_free = IsTableOidUnused(table_oid, + reltablespace, + pg_class_desc, + relpersistence); + heap_close(pg_class_desc, RowExclusiveLock); + + if (is_oid_free) + return table_oid; + else + elog(ERROR, "Oid %d is in use.", table_oid); + + /* Only process the first table_oid. */ + break; + } } + } - pfree(rpath); - } while (collides); - - return rnode.node.relNode; + return InvalidOid; } diff --git a/src/postgres/src/backend/commands/indexcmds.c b/src/postgres/src/backend/commands/indexcmds.c index f43929731f66..89ad1a7a3212 100644 --- a/src/postgres/src/backend/commands/indexcmds.c +++ b/src/postgres/src/backend/commands/indexcmds.c @@ -994,6 +994,13 @@ DefineIndex(Oid relationId, if (stmt->initdeferred) constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED; + /* Check for WITH (table_oid = x). */ + if (!OidIsValid(indexRelationId) && stmt->relation) + { + indexRelationId = GetTableOidFromRelOptions( + stmt->options, tablespaceId, stmt->relation->relpersistence); + } + indexRelationId = index_create(rel, indexRelationName, indexRelationId, parentIndexId, parentConstraintId, diff --git a/src/postgres/src/backend/commands/tablecmds.c b/src/postgres/src/backend/commands/tablecmds.c index d81461924c75..72826246d6f0 100644 --- a/src/postgres/src/backend/commands/tablecmds.c +++ b/src/postgres/src/backend/commands/tablecmds.c @@ -820,6 +820,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, attr->attidentity = colDef->identity; } + /* Check for WITH (table_oid = x). */ + relationId = GetTableOidFromRelOptions( + stmt->options, tablespaceId, stmt->relation->relpersistence); + /* * Create the relation. Inherited defaults and constraints are passed in * for immediate handling --- since they don't need parsing, they can be @@ -828,7 +832,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, relationId = heap_create_with_catalog(relname, namespaceId, tablespaceId, - InvalidOid, + relationId, InvalidOid, ofTypeId, ownerId, diff --git a/src/postgres/src/backend/commands/ybccmds.c b/src/postgres/src/backend/commands/ybccmds.c index 557fd0f914a1..6ad4a9c690be 100644 --- a/src/postgres/src/backend/commands/ybccmds.c +++ b/src/postgres/src/backend/commands/ybccmds.c @@ -697,21 +697,6 @@ YBCCreateIndex(const char *indexName, schema_name, indexName); - /* Check reloptions. */ - ListCell *opt_cell; - foreach(opt_cell, untransformRelOptions(reloptions)) - { - DefElem *def = (DefElem *) lfirst(opt_cell); - - if (strcmp(def->defname, "colocated") == 0) - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot set option \"%s\" on index", - def->defname))); - } - } - YBCPgStatement handle = NULL; HandleYBStatus(YBCPgNewCreateIndex(db_name, diff --git a/src/postgres/src/backend/parser/parse_utilcmd.c b/src/postgres/src/backend/parser/parse_utilcmd.c index 1192968bdc57..ee2044ffb525 100644 --- a/src/postgres/src/backend/parser/parse_utilcmd.c +++ b/src/postgres/src/backend/parser/parse_utilcmd.c @@ -359,6 +359,21 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) } else if (strcmp(def->defname, "colocated") == 0) (void) defGetBoolean(def); + else if (strcmp(def->defname, "table_oid") == 0) + { + if (!yb_enable_create_with_table_oid) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("Create table with oid is not allowed."), + errhint("Try enabling the session variable yb_enable_create_with_table_oid."))); + } + Oid table_oid = defGetInt32(def); + if (table_oid < FirstNormalObjectId) + { + elog(ERROR, "User tables must have an OID >= %d.", + FirstNormalObjectId); + } + } else ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -2681,6 +2696,21 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Cannot supply tablegroup through WITH clause."))); } + else if (strcmp(def->defname, "table_oid") == 0) + { + if (!yb_enable_create_with_table_oid) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("Create index with oid is not allowed."), + errhint("Try enabling the session variable yb_enable_create_with_table_oid."))); + } + Oid table_oid = defGetInt32(def); + if (table_oid < FirstNormalObjectId) + { + elog(ERROR, "User tables must have an OID >= %d.", + FirstNormalObjectId); + } + } } /* diff --git a/src/postgres/src/backend/utils/misc/guc.c b/src/postgres/src/backend/utils/misc/guc.c index accea2b3d663..edf1b903b21f 100644 --- a/src/postgres/src/backend/utils/misc/guc.c +++ b/src/postgres/src/backend/utils/misc/guc.c @@ -1883,6 +1883,17 @@ static struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, + { + {"yb_enable_create_with_table_oid", PGC_USERSET, CUSTOM_OPTIONS, + gettext_noop("Enables the ability to set table oids when creating tables or indexes."), + NULL, + GUC_NOT_IN_SAMPLE + }, + &yb_enable_create_with_table_oid, + false, + NULL, NULL, NULL + }, + { {"data_sync_retry", PGC_POSTMASTER, ERROR_HANDLING_OPTIONS, gettext_noop("Whether to continue running after a failure to sync data files."), diff --git a/src/postgres/src/backend/utils/misc/pg_yb_utils.c b/src/postgres/src/backend/utils/misc/pg_yb_utils.c index dea7fa652f8f..7831e18e8d75 100644 --- a/src/postgres/src/backend/utils/misc/pg_yb_utils.c +++ b/src/postgres/src/backend/utils/misc/pg_yb_utils.c @@ -782,6 +782,11 @@ PowerWithUpperLimit(double base, int exp, double upper_limit) return res; } +//------------------------------------------------------------------------------ +// YB GUC variables. + +bool yb_enable_create_with_table_oid = false; + //------------------------------------------------------------------------------ // YB Debug utils. diff --git a/src/postgres/src/include/catalog/catalog.h b/src/postgres/src/include/catalog/catalog.h index d1feb7ba8ab5..c1b442001538 100644 --- a/src/postgres/src/include/catalog/catalog.h +++ b/src/postgres/src/include/catalog/catalog.h @@ -47,4 +47,7 @@ extern Oid GetNewOidWithIndex(Relation relation, Oid indexId, extern Oid GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence); +extern Oid GetTableOidFromRelOptions(List *relOptions, Oid reltablespace, + char relpersistence); + #endif /* CATALOG_H */ diff --git a/src/postgres/src/include/pg_yb_utils.h b/src/postgres/src/include/pg_yb_utils.h index 35868f54ee97..0ad1f243919f 100644 --- a/src/postgres/src/include/pg_yb_utils.h +++ b/src/postgres/src/include/pg_yb_utils.h @@ -323,6 +323,17 @@ void YBRaiseNotSupportedSignal(const char *msg, int issue_no, int signal_level); */ extern double PowerWithUpperLimit(double base, int exponent, double upper_limit); +//------------------------------------------------------------------------------ +// YB GUC variables. + +/** + * YSQL guc variables that can be used to toggle yugabyte features. + * See also the corresponding entries in guc.c. + */ + +/* Enables tables/indexes to be created WITH (table_oid = x). */ +extern bool yb_enable_create_with_table_oid; + //------------------------------------------------------------------------------ // YB Debug utils. diff --git a/src/postgres/src/include/utils/rel.h b/src/postgres/src/include/utils/rel.h index 91585b95e750..3dfc6d4a5610 100644 --- a/src/postgres/src/include/utils/rel.h +++ b/src/postgres/src/include/utils/rel.h @@ -265,7 +265,8 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ bool colocated; - int tablegroup; + int tablegroup; + int table_oid; } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 @@ -330,6 +331,15 @@ typedef struct StdRdOptions ((relation)->rd_options ? \ ((StdRdOptions *) (relation)->rd_options)->tablegroup : 0) +/* + * RelationGetTableOid + * Returns the relation's table_oid reloption setting. + * Note multiple eval of argument! + */ +#define RelationGetTableOid(relation) \ + ((relation)->rd_options ? \ + ((StdRdOptions *) (relation)->rd_options)->table_oid : 0) + /* * ViewOptions diff --git a/src/postgres/src/test/regress/expected/create_table.out b/src/postgres/src/test/regress/expected/create_table.out index 8cd148601d29..4fbdc23e1cbc 100644 --- a/src/postgres/src/test/regress/expected/create_table.out +++ b/src/postgres/src/test/regress/expected/create_table.out @@ -223,7 +223,7 @@ ERROR: unrecognized parameter "Oids" CREATE UNLOGGED TABLE unlogged1 (a int primary key); -- OK CREATE TEMPORARY TABLE unlogged2 (a int primary key); -- OK SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname; - relname | relkind | relpersistence + relname | relkind | relpersistence ----------------+---------+---------------- unlogged1 | r | u unlogged1_pkey | i | u @@ -234,7 +234,7 @@ SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged REINDEX INDEX unlogged1_pkey; REINDEX INDEX unlogged2_pkey; SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname; - relname | relkind | relpersistence + relname | relkind | relpersistence ----------------+---------+---------------- unlogged1 | r | u unlogged1_pkey | i | u @@ -268,7 +268,7 @@ DROP TABLE as_select1; \set ECHO none INSERT INTO extra_wide_table(firstc, lastc) VALUES('first col', 'last col'); SELECT firstc, lastc FROM extra_wide_table; - firstc | lastc + firstc | lastc -----------+---------- first col | last col (1 row) @@ -390,7 +390,7 @@ CREATE TABLE partitioned ( ) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "C"); -- check relkind SELECT relkind FROM pg_class WHERE relname = 'partitioned'; - relkind + relkind --------- p (1 row) @@ -410,21 +410,21 @@ ERROR: cannot inherit from partitioned table "partitioned2" -- Partition key in describe output \d partitioned Table "public.partitioned" - Column | Type | Collation | Nullable | Default + Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - c | text | | | - d | text | | | + a | integer | | | + b | integer | | | + c | text | | | + d | text | | | Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "C") Number of partitions: 0 \d+ partitioned2 Table "public.partitioned2" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+------------- - a | integer | | | | plain | | - b | text | | | | extended | | + a | integer | | | | plain | | + b | text | | | | extended | | Partition key: RANGE (((a + 1)), substr(b, 1, 5)) Number of partitions: 0 @@ -434,10 +434,10 @@ DETAIL: Partition key of the failing row contains ((a + 1), substr(b, 1, 5)) = CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO (100, 'ccccc'); \d+ part2_1 Table "public.part2_1" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+------------- - a | integer | | | | plain | | - b | text | | | | extended | | + a | integer | | | | plain | | + b | text | | | | extended | | Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc') Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text)))) @@ -596,9 +596,9 @@ CREATE TABLE oids_parted ( CREATE TABLE part_forced_oids PARTITION OF oids_parted FOR VALUES FROM (1) TO (10) WITHOUT OIDS; \d+ part_forced_oids Table "public.part_forced_oids" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | + a | integer | | | | plain | | Partition of: oids_parted FOR VALUES FROM (1) TO (10) Partition constraint: ((a IS NOT NULL) AND (a >= 1) AND (a < 10)) Has OIDs: yes @@ -699,7 +699,7 @@ CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a'); SELECT attname, attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_a'::regclass and attnum > 0 ORDER BY attnum; - attname | attislocal | attinhcount + attname | attislocal | attinhcount ---------+------------+------------- a | f | 1 b | f | 1 @@ -721,7 +721,7 @@ CREATE TABLE part_b PARTITION OF parted ( NOTICE: merging constraint "check_a" with inherited definition -- conislocal should be false for any merged constraints SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_a'; - conislocal | coninhcount + conislocal | coninhcount ------------+------------- f | 1 (1 row) @@ -741,7 +741,7 @@ DETAIL: Failing row contains (1, null). -- note that while b's default is overriden, a's default is preserved \d parted_notnull_inh_test1 Table "public.parted_notnull_inh_test1" - Column | Type | Collation | Nullable | Default + Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- a | integer | | not null | 1 b | integer | | not null | 1 @@ -761,10 +761,10 @@ drop table parted_collate_must_match; -- Partition bound in describe output \d+ part_b Table "public.part_b" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+------------- - a | text | | | | extended | | - b | integer | | not null | 1 | plain | | + a | text | | | | extended | | + b | integer | | not null | 1 | plain | | Partition of: parted FOR VALUES IN ('b') Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text)) Check constraints: @@ -774,10 +774,10 @@ Check constraints: -- Both partition bound and partition key in describe output \d+ part_c Table "public.part_c" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+------------- - a | text | | | | extended | | - b | integer | | not null | 0 | plain | | + a | text | | | | extended | | + b | integer | | not null | 0 | plain | | Partition of: parted FOR VALUES IN ('c') Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text)) Partition key: RANGE (b) @@ -788,10 +788,10 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10) -- a level-2 partition's constraint will include the parent's expressions \d+ part_c_1_10 Table "public.part_c_1_10" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+------------- - a | text | | | | extended | | - b | integer | | not null | 0 | plain | | + a | text | | | | extended | | + b | integer | | not null | 0 | plain | | Partition of: part_c FOR VALUES FROM (1) TO (10) Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10)) Check constraints: @@ -803,9 +803,9 @@ Check constraints: -- returned. \d parted Table "public.parted" - Column | Type | Collation | Nullable | Default + Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- - a | text | | | + a | text | | | b | integer | | not null | 0 Partition key: LIST (a) Check constraints: @@ -814,9 +814,9 @@ Number of partitions: 3 (Use \d+ to list them.) \d hash_parted Table "public.hash_parted" - Column | Type | Collation | Nullable | Default + Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- - a | integer | | | + a | integer | | | Partition key: HASH (a) Number of partitions: 3 (Use \d+ to list them.) @@ -825,11 +825,11 @@ CREATE TABLE range_parted4 (a int, b int, c int) PARTITION BY RANGE (abs(a), abs CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE); \d+ unbounded_range_part Table "public.unbounded_range_part" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | - b | integer | | | | plain | | - c | integer | | | | plain | | + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | | | plain | | Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE) Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL)) @@ -837,33 +837,33 @@ DROP TABLE unbounded_range_part; CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE); \d+ range_parted4_1 Table "public.range_parted4_1" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | - b | integer | | | | plain | | - c | integer | | | | plain | | + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | | | plain | | Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE) Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1)) CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE); \d+ range_parted4_2 Table "public.range_parted4_2" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | - b | integer | | | | plain | | - c | integer | | | | plain | | + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | | | plain | | Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE) Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7)))) CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE); \d+ range_parted4_3 Table "public.range_parted4_3" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | - b | integer | | | | plain | | - c | integer | | | | plain | | + a | integer | | | | plain | | + b | integer | | | | plain | | + c | integer | | | | plain | | Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE) Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9)) @@ -889,17 +889,17 @@ CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a); COMMENT ON TABLE parted_col_comment IS 'Am partitioned table'; COMMENT ON COLUMN parted_col_comment.a IS 'Partition key'; SELECT obj_description('parted_col_comment'::regclass); - obj_description + obj_description ---------------------- Am partitioned table (1 row) \d+ parted_col_comment Table "public.parted_col_comment" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+--------------- a | integer | | | | plain | | Partition key - b | text | | | | extended | | + b | text | | | | extended | | Partition key: LIST (a) Number of partitions: 0 @@ -909,9 +909,9 @@ CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a); CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}'); \d+ arrlp12 Table "public.arrlp12" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+-----------+-----------+----------+---------+----------+--------------+------------- - a | integer[] | | | | extended | | + a | integer[] | | | | extended | | Partition of: arrlp FOR VALUES IN ('{1}', '{2}') Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[]))) @@ -922,9 +922,9 @@ create table boolspart_t partition of boolspart for values in (true); create table boolspart_f partition of boolspart for values in (false); \d+ boolspart Table "public.boolspart" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+---------+--------------+------------- - a | boolean | | | | plain | | + a | boolean | | | | plain | | Partition key: LIST (a) Partitions: boolspart_f FOR VALUES IN (false), boolspart_t FOR VALUES IN (true) diff --git a/src/postgres/src/test/regress/expected/yb_create_index.out b/src/postgres/src/test/regress/expected/yb_create_index.out index eb598b1effa3..6c969aa46ebd 100644 --- a/src/postgres/src/test/regress/expected/yb_create_index.out +++ b/src/postgres/src/test/regress/expected/yb_create_index.out @@ -1031,3 +1031,42 @@ SELECT v FROM tbl WHERE v >= 10 and v < 40 ORDER BY v DESC; 15 10 (9 rows) + +-- Test creating indexes with (table_oid = x) +CREATE TABLE test_index_with_oids (v1 INT, v2 INT, v3 INT); +INSERT INTO test_index_with_oids VALUES (1, 11, 21), (2, 12, 22), (3, 13, 23), (4, 14, 24), (5, 15, 25); +-- Test with variable = false +CREATE INDEX index_with_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +ERROR: Create index with oid is not allowed. +HINT: Try enabling the session variable yb_enable_create_with_table_oid. +-- Turn on variable and test +set yb_enable_create_with_table_oid=1; +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 0); +ERROR: User tables must have an OID >= 16384. +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = -1); +ERROR: value -1 out of bounds for option "table_oid" +DETAIL: Valid values are between "16384" and "2147483647". +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 123); +ERROR: User tables must have an OID >= 16384. +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 'test'); +ERROR: table_oid requires an integer value +CREATE INDEX index_with_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +select relname, oid from pg_class where relname = 'index_with_table_oid'; + relname | oid +----------------------+--------- + index_with_table_oid | 1111111 +(1 row) + +SELECT * FROM test_index_with_oids ORDER BY v1; + v1 | v2 | v3 +----+----+---- + 1 | 11 | 21 + 2 | 12 | 22 + 3 | 13 | 23 + 4 | 14 | 24 + 5 | 15 | 25 +(5 rows) + +CREATE INDEX index_with_duplicate_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +ERROR: Oid 1111111 is in use. +set yb_enable_create_with_table_oid=0; diff --git a/src/postgres/src/test/regress/expected/yb_create_table.out b/src/postgres/src/test/regress/expected/yb_create_table.out index 466a11f95859..4ecc4e1cdb97 100644 --- a/src/postgres/src/test/regress/expected/yb_create_table.out +++ b/src/postgres/src/test/regress/expected/yb_create_table.out @@ -659,3 +659,50 @@ SELECT k FROM ordered_desc WHERE k > 10 and k < 40 ORDER BY k ASC; 35 36 (8 rows) + +-- Test create ... with (table_oid = x) +set yb_enable_create_with_table_oid=1; +create table with_invalid_table_oid (a int) with (table_oid = 0); +ERROR: User tables must have an OID >= 16384. +create table with_invalid_table_oid (a int) with (table_oid = -1); +ERROR: value -1 out of bounds for option "table_oid" +DETAIL: Valid values are between "16384" and "2147483647". +create table with_invalid_table_oid (a int) with (table_oid = 123); +ERROR: User tables must have an OID >= 16384. +create table with_invalid_table_oid (a int) with (table_oid = 'test'); +ERROR: table_oid requires an integer value +create table with_table_oid (a int) with (table_oid = 1234567); +select relname, oid from pg_class where relname = 'with_table_oid'; + relname | oid +----------------+--------- + with_table_oid | 1234567 +(1 row) + +create table with_table_oid_duplicate (a int) with (table_oid = 1234567); +ERROR: Oid 1234567 is in use. +-- Test temp tables with (table_oid = x) +begin; +create temp table with_table_oid_temp (a int) with (table_oid = 1234568) on commit drop; +select relname, oid from pg_class where relname = 'with_table_oid_temp'; + relname | oid +---------------------+--------- + with_table_oid_temp | 1234568 +(1 row) + +end; +-- Creating a new temp table with that oid will fail +create temp table with_table_oid_temp_2 (a int) with (table_oid = 1234568); +ERROR: Oid 1234568 is in use. +-- But creating a regular table with that oid should succeed +create table with_table_oid_2 (a int) with (table_oid = 1234568); +select relname, oid from pg_class where relname = 'with_table_oid_2'; + relname | oid +------------------+--------- + with_table_oid_2 | 1234568 +(1 row) + +-- Test with session variable off +set yb_enable_create_with_table_oid=0; +create table with_table_oid_variable_false (a int) with (table_oid = 55555); +ERROR: Create table with oid is not allowed. +HINT: Try enabling the session variable yb_enable_create_with_table_oid. diff --git a/src/postgres/src/test/regress/expected/yb_feature_colocation.out b/src/postgres/src/test/regress/expected/yb_feature_colocation.out index e89736592073..34f89d43e722 100644 --- a/src/postgres/src/test/regress/expected/yb_feature_colocation.out +++ b/src/postgres/src/test/regress/expected/yb_feature_colocation.out @@ -178,11 +178,11 @@ SELECT * FROM tab_range_nonkey2; -- colocated table with non-colocated index CREATE TABLE tab_range_nonkey3 (a INT, b INT, PRIMARY KEY (a ASC)); CREATE INDEX idx_range_colo ON tab_range_nonkey3 (a) WITH (colocated = true); -ERROR: cannot set option "colocated" on index +ERROR: unrecognized parameter "colocated" -- colocated table with colocated index CREATE TABLE tab_range_nonkey4 (a INT, b INT, PRIMARY KEY (a ASC)); CREATE INDEX idx_range_noco ON tab_range_nonkey4 (a) WITH (colocated = false); -ERROR: cannot set option "colocated" on index +ERROR: unrecognized parameter "colocated" -- non-colocated table with index CREATE TABLE tab_range_nonkey_noco (a INT, b INT, PRIMARY KEY (a ASC)) WITH (colocated = false); CREATE INDEX idx_range2 ON tab_range_nonkey_noco (a); diff --git a/src/postgres/src/test/regress/sql/yb_create_index.sql b/src/postgres/src/test/regress/sql/yb_create_index.sql index 3ba046c2f148..84e7bacc7be4 100644 --- a/src/postgres/src/test/regress/sql/yb_create_index.sql +++ b/src/postgres/src/test/regress/sql/yb_create_index.sql @@ -71,7 +71,7 @@ CREATE TABLE t2 (h INT, r INT, v1 INT, v2 INT, PRIMARY KEY (h hash, r)); \d t1 \d t2 -INSERT INTO t1 VALUES (1, 1, 11, 11), (1, 2, 11, 12); +INSERT INTO t1 VALUES (1, 1, 11, 11), (1, 2, 11, 12); INSERT INTO t2 VALUES (1, 1, 21, 21); -- The following 2 inserts should produce error due to duplicate primary key / unique index value @@ -311,3 +311,23 @@ EXPLAIN (COSTS OFF) SELECT v FROM tbl WHERE v >= 10 and v < 40 ORDER BY v ASC; SELECT v FROM tbl WHERE v >= 10 and v < 40 ORDER BY v ASC; EXPLAIN (COSTS OFF) SELECT v FROM tbl WHERE v >= 10 and v < 40 ORDER BY v DESC; SELECT v FROM tbl WHERE v >= 10 and v < 40 ORDER BY v DESC; + +-- Test creating indexes with (table_oid = x) +CREATE TABLE test_index_with_oids (v1 INT, v2 INT, v3 INT); +INSERT INTO test_index_with_oids VALUES (1, 11, 21), (2, 12, 22), (3, 13, 23), (4, 14, 24), (5, 15, 25); + +-- Test with variable = false +CREATE INDEX index_with_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +-- Turn on variable and test +set yb_enable_create_with_table_oid=1; +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 0); +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = -1); +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 123); +CREATE INDEX index_with_invalid_oid ON test_index_with_oids (v1) with (table_oid = 'test'); + +CREATE INDEX index_with_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +select relname, oid from pg_class where relname = 'index_with_table_oid'; +SELECT * FROM test_index_with_oids ORDER BY v1; + +CREATE INDEX index_with_duplicate_table_oid ON test_index_with_oids (v1) with (table_oid = 1111111); +set yb_enable_create_with_table_oid=0; diff --git a/src/postgres/src/test/regress/sql/yb_create_table.sql b/src/postgres/src/test/regress/sql/yb_create_table.sql index 457cb29d5ff2..a96efc7823bb 100644 --- a/src/postgres/src/test/regress/sql/yb_create_table.sql +++ b/src/postgres/src/test/regress/sql/yb_create_table.sql @@ -513,3 +513,30 @@ EXPLAIN (COSTS OFF) SELECT * FROM ordered_desc ORDER BY k DESC; SELECT * FROM ordered_desc ORDER BY k DESC; EXPLAIN (COSTS OFF) SELECT k FROM ordered_desc WHERE k > 10 and k < 40 ORDER BY k ASC; SELECT k FROM ordered_desc WHERE k > 10 and k < 40 ORDER BY k ASC; + +-- Test create ... with (table_oid = x) +set yb_enable_create_with_table_oid=1; +create table with_invalid_table_oid (a int) with (table_oid = 0); +create table with_invalid_table_oid (a int) with (table_oid = -1); +create table with_invalid_table_oid (a int) with (table_oid = 123); +create table with_invalid_table_oid (a int) with (table_oid = 'test'); + +create table with_table_oid (a int) with (table_oid = 1234567); +select relname, oid from pg_class where relname = 'with_table_oid'; + +create table with_table_oid_duplicate (a int) with (table_oid = 1234567); + +-- Test temp tables with (table_oid = x) +begin; +create temp table with_table_oid_temp (a int) with (table_oid = 1234568) on commit drop; +select relname, oid from pg_class where relname = 'with_table_oid_temp'; +end; +-- Creating a new temp table with that oid will fail +create temp table with_table_oid_temp_2 (a int) with (table_oid = 1234568); +-- But creating a regular table with that oid should succeed +create table with_table_oid_2 (a int) with (table_oid = 1234568); +select relname, oid from pg_class where relname = 'with_table_oid_2'; + +-- Test with session variable off +set yb_enable_create_with_table_oid=0; +create table with_table_oid_variable_false (a int) with (table_oid = 55555);