Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/dump/dump_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ func TestDumpCommand_Issue78ConstraintNotValid(t *testing.T) {
runExactMatchTest(t, "issue_78_constraint_not_valid")
}

func TestDumpCommand_Issue80IndexNameQuote(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
runExactMatchTest(t, "issue_80_index_name_quote")
}

func runExactMatchTest(t *testing.T, testDataDir string) {
runExactMatchTestWithContext(t, context.Background(), testDataDir)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/diff/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func generateIndexSQLWithName(index *ir.Index, indexName string, targetSchema st
builder.WriteString("IF NOT EXISTS ")

// Index name
builder.WriteString(indexName)
builder.WriteString(ir.QuoteIdentifier(indexName))
builder.WriteString(" ON ")

// Table name with proper schema qualification
Expand Down
10 changes: 5 additions & 5 deletions internal/plan/rewrite.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ func generateIndexChangeRewriteFromIndex(index *ir.Index) []RewriteStep {
waitSQL := generateIndexWaitQueryWithName(tempIndexName)

// Drop old index and rename new one
dropSQL := fmt.Sprintf("DROP INDEX %s;", index.Name)
renameSQL := fmt.Sprintf("ALTER INDEX %s RENAME TO %s;", tempIndexName, index.Name)
dropSQL := fmt.Sprintf("DROP INDEX %s;", ir.QuoteIdentifier(index.Name))
renameSQL := fmt.Sprintf("ALTER INDEX %s RENAME TO %s;", ir.QuoteIdentifier(tempIndexName), ir.QuoteIdentifier(index.Name))

return []RewriteStep{
{
Expand Down Expand Up @@ -162,8 +162,8 @@ func generateIndexChangeRewrite(indexDiff *diff.IndexDiff) []RewriteStep {
waitSQL := generateIndexWaitQueryWithName(tempIndexName)

// Drop old index and rename new one
dropSQL := fmt.Sprintf("DROP INDEX %s;", indexDiff.Old.Name)
renameSQL := fmt.Sprintf("ALTER INDEX %s RENAME TO %s;", tempIndexName, indexDiff.New.Name)
dropSQL := fmt.Sprintf("DROP INDEX %s;", ir.QuoteIdentifier(indexDiff.Old.Name))
renameSQL := fmt.Sprintf("ALTER INDEX %s RENAME TO %s;", ir.QuoteIdentifier(tempIndexName), ir.QuoteIdentifier(indexDiff.New.Name))

return []RewriteStep{
{
Expand Down Expand Up @@ -322,7 +322,7 @@ func generateIndexSQL(index *ir.Index, isConcurrent bool) string {
sql.WriteString(" CONCURRENTLY")
}
sql.WriteString(" IF NOT EXISTS ")
sql.WriteString(index.Name)
sql.WriteString(ir.QuoteIdentifier(index.Name))
sql.WriteString(" ON ")

tableName := getTableNameWithSchema(index.Schema, index.Table)
Expand Down
8 changes: 8 additions & 0 deletions testdata/dump/issue_80_index_name_quote/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "issue_80_index_name_quote",
"description": "Test case for index name quoting (GitHub issue #80)",
"source": "https://github.com/pgschema/pgschema/issues/80",
"notes": [
"Tests that index names with spaces, special characters, and mixed case are properly quoted"
]
}
140 changes: 140 additions & 0 deletions testdata/dump/issue_80_index_name_quote/pgdump.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
--
-- PostgreSQL database dump
--

-- Dumped from database version 17.5 (Debian 17.5-1.pgdg120+1)
-- Dumped by pg_dump version 17.5 (Homebrew)

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;

SET default_tablespace = '';

SET default_table_access_method = heap;

--
-- Name: products; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.products (
id uuid DEFAULT gen_random_uuid() NOT NULL,
name text NOT NULL,
price numeric(10,2),
category text
);


--
-- Name: users; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.users (
id uuid DEFAULT gen_random_uuid() NOT NULL,
email text NOT NULL,
username text NOT NULL,
created_at timestamp without time zone DEFAULT now(),
status text,
"position" integer,
department text
);


--
-- Name: products products_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.products
ADD CONSTRAINT products_pkey PRIMARY KEY (id);


--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);


--
-- Name: UPPER name search; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX "UPPER name search" ON public.products USING btree (upper(name));


--
-- Name: UserDepartmentIndex; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX "UserDepartmentIndex" ON public.users USING btree (department);


--
-- Name: active users index; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX "active users index" ON public.users USING btree (status) WHERE (status = 'active'::text);


--
-- Name: email+username combo; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX "email+username combo" ON public.users USING btree (email, username);


--
-- Name: order; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX "order" ON public.products USING btree (price DESC);


--
-- Name: products_category_idx_v2; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX products_category_idx_v2 ON public.products USING btree (category);


--
-- Name: user email index; Type: INDEX; Schema: public; Owner: -
--

CREATE UNIQUE INDEX "user email index" ON public.users USING btree (email);


--
-- Name: user-status-index; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX "user-status-index" ON public.users USING btree (status);


--
-- Name: users.position.idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX "users.position.idx" ON public.users USING btree ("position");


--
-- Name: users_created_at_idx; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX users_created_at_idx ON public.users USING btree (created_at);


--
-- PostgreSQL database dump complete
--

93 changes: 93 additions & 0 deletions testdata/dump/issue_80_index_name_quote/pgschema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
--
-- pgschema database dump
--

-- Dumped from database version PostgreSQL 17.5
-- Dumped by pgschema version 1.3.0


--
-- Name: products; Type: TABLE; Schema: -; Owner: -
--

CREATE TABLE IF NOT EXISTS products (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
name text NOT NULL,
price numeric(10,2),
category text
);

--
-- Name: UPPER name search; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS "UPPER name search" ON products (upper(name));

--
-- Name: order; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS "order" ON products (price DESC);

--
-- Name: products_category_idx_v2; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS products_category_idx_v2 ON products (category);

--
-- Name: users; Type: TABLE; Schema: -; Owner: -
--

CREATE TABLE IF NOT EXISTS users (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
email text NOT NULL,
username text NOT NULL,
created_at timestamp DEFAULT now(),
status text,
position integer,
department text
);

--
-- Name: UserDepartmentIndex; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS "UserDepartmentIndex" ON users (department);

--
-- Name: active users index; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS "active users index" ON users (status) WHERE (status = 'active'::text);

--
-- Name: email+username combo; Type: INDEX; Schema: -; Owner: -
--

CREATE UNIQUE INDEX IF NOT EXISTS "email+username combo" ON users (email, username);

--
-- Name: user email index; Type: INDEX; Schema: -; Owner: -
--

CREATE UNIQUE INDEX IF NOT EXISTS "user email index" ON users (email);

--
-- Name: user-status-index; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS "user-status-index" ON users (status);

--
-- Name: idx; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS "users.position.idx" ON users (position);

--
-- Name: users_created_at_idx; Type: INDEX; Schema: -; Owner: -
--

CREATE INDEX IF NOT EXISTS users_created_at_idx ON users (created_at);

84 changes: 84 additions & 0 deletions testdata/dump/issue_80_index_name_quote/raw.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
--
-- Test case for GitHub issue #80: Index name quoting
--
-- This demonstrates various scenarios where index names need quoting:
-- 1. Index names with spaces
-- 2. Index names with special characters (hyphens, dots, etc.)
-- 3. Index names with mixed case that requires quoting
-- 4. Normal index names that don't require quoting (control case)
--

--
-- Test table for index quoting scenarios
--
CREATE TABLE users (
id uuid DEFAULT gen_random_uuid() NOT NULL,
email text NOT NULL,
username text NOT NULL,
created_at timestamp DEFAULT now(),
status text,
position integer,
department text,
CONSTRAINT users_pkey PRIMARY KEY (id)
);

--
-- Case 1: Index name with spaces (from the original issue)
--
CREATE UNIQUE INDEX "user email index" ON users (email);

--
-- Case 2: Index name with hyphens (special character)
--
CREATE INDEX "user-status-index" ON users (status);

--
-- Case 3: Index name with dots
--
CREATE INDEX "users.position.idx" ON users (position);

--
-- Case 4: Mixed case requiring quotes
--
CREATE INDEX "UserDepartmentIndex" ON users (department);

--
-- Case 5: Normal index name (no quotes needed)
--
CREATE INDEX users_created_at_idx ON users (created_at);

--
-- Case 6: Partial index with quoted name and spaces
--
CREATE INDEX "active users index" ON users (status) WHERE status = 'active';

--
-- Case 7: Multi-column index with special name
--
CREATE UNIQUE INDEX "email+username combo" ON users (email, username);

--
-- Additional test table for more complex scenarios
--
CREATE TABLE products (
id uuid DEFAULT gen_random_uuid() NOT NULL,
name text NOT NULL,
price numeric(10,2),
category text,
CONSTRAINT products_pkey PRIMARY KEY (id)
);

--
-- Case 8: Functional index with quoted name
--
CREATE INDEX "UPPER name search" ON products (upper(name));

--
-- Case 9: Index with numbers and underscores (doesn't need quotes)
--
CREATE INDEX products_category_idx_v2 ON products (category);

--
-- Case 10: Index name that looks like a keyword (needs quotes)
--
CREATE INDEX "order" ON products (price DESC);