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
72 changes: 57 additions & 15 deletions ir/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -1071,35 +1071,77 @@ func IsTextLikeType(typeName string) bool {
// Example transformations:
// - "status = ANY (ARRAY['active'::public.status_type])" → "status IN ('active'::public.status_type)"
// - "gender = ANY (ARRAY['M'::text, 'F'::text])" → "gender IN ('M'::text, 'F'::text)"
// - "(col = ANY (ARRAY[...])) AND (other)" → "(col IN (...)) AND (other)"
func convertAnyArrayToIn(expr string) string {
if !strings.Contains(expr, "= ANY (ARRAY[") {
const marker = " = ANY (ARRAY["
idx := strings.Index(expr, marker)
if idx == -1 {
return expr
}

// Extract the column name and values
parts := strings.Split(expr, " = ANY (ARRAY[")
if len(parts) != 2 {
// Extract the part before the marker (column name with possible leading content)
prefix := expr[:idx]

// Find the closing "])" for ARRAY[...] starting after the marker
startIdx := idx + len(marker)
arrayEnd := findArrayClose(expr, startIdx)
if arrayEnd == -1 {
return expr
}

columnName := strings.TrimSpace(parts[0])

// Remove the closing parentheses and brackets
valuesPart := parts[1]
valuesPart = strings.TrimSuffix(valuesPart, "])")
valuesPart = strings.TrimSuffix(valuesPart, "])) ")
valuesPart = strings.TrimSuffix(valuesPart, "]))")
valuesPart = strings.TrimSuffix(valuesPart, "])")
// Extract array contents and any trailing expression
arrayContents := expr[startIdx:arrayEnd]
suffix := expr[arrayEnd+2:] // skip "])"

// Split values and preserve them as-is, including all type casts
values := strings.Split(valuesPart, ", ")
values := strings.Split(arrayContents, ", ")
var cleanValues []string
for _, val := range values {
val = strings.TrimSpace(val)
cleanValues = append(cleanValues, val)
}

// Return converted format: "column IN ('val1'::type, 'val2'::type)"
return fmt.Sprintf("%s IN (%s)", columnName, strings.Join(cleanValues, ", "))
// Return converted format: "prefix IN (values)suffix"
return fmt.Sprintf("%s IN (%s)%s", prefix, strings.Join(cleanValues, ", "), suffix)
}

// findArrayClose finds the position of the closing "])" for an ARRAY literal,
// handling nested brackets and quoted strings properly.
func findArrayClose(expr string, startIdx int) int {
bracketDepth := 1 // We're already inside ARRAY[
inQuote := false

for i := startIdx; i < len(expr); i++ {
ch := expr[i]

if inQuote {
if ch == '\'' {
// Check for escaped quote ''
if i+1 < len(expr) && expr[i+1] == '\'' {
i++ // Skip escaped quote
continue
}
inQuote = false
}
continue
}

switch ch {
case '\'':
inQuote = true
case '[':
bracketDepth++
case ']':
bracketDepth--
if bracketDepth == 0 {
// Found the closing ], verify next char is )
if i+1 < len(expr) && expr[i+1] == ')' {
return i // Return position of ]
}
}
}
}

return -1 // Not found
}

2 changes: 1 addition & 1 deletion testdata/diff/online/add_partial_index/diff.sql
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CREATE INDEX IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status);
CREATE INDEX IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE (status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status)) AND (is_active IS NOT NULL);
3 changes: 2 additions & 1 deletion testdata/diff/online/add_partial_index/new.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ CREATE TABLE public.orders (
order_date date,
total_amount numeric(10,2),
payment_status text,
is_active boolean,
created_at timestamp with time zone,
updated_at timestamp with time zone
);

CREATE INDEX idx_active_orders_customer_date ON public.orders USING btree (customer_id, order_date DESC, total_amount) WHERE status IN ('pending'::order_status, 'processing'::order_status, 'confirmed'::order_status);
CREATE INDEX idx_active_orders_customer_date ON public.orders USING btree (customer_id, order_date DESC, total_amount) WHERE (status IN ('pending'::order_status, 'processing'::order_status, 'confirmed'::order_status)) AND (is_active IS NOT NULL);
1 change: 1 addition & 0 deletions testdata/diff/online/add_partial_index/old.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CREATE TABLE public.orders (
order_date date,
total_amount numeric(10,2),
payment_status text,
is_active boolean,
created_at timestamp with time zone,
updated_at timestamp with time zone
);
4 changes: 2 additions & 2 deletions testdata/diff/online/add_partial_index/plan.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"pgschema_version": "1.6.1",
"created_at": "1970-01-01T00:00:00Z",
"source_fingerprint": {
"hash": "40ed646f28de84540200a1acd21fc416700f08c6779bb8f105b423be70a5df8c"
"hash": "d3818157c6994cfe669982e5174d7e36ada8c106bb8d340c392c7cbe64ebc135"
},
"groups": [
{
"steps": [
{
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status);",
"sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE (status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status)) AND (is_active IS NOT NULL);",
"type": "table.index",
"operation": "create",
"path": "public.orders.idx_active_orders_customer_date"
Expand Down
2 changes: 1 addition & 1 deletion testdata/diff/online/add_partial_index/plan.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE (status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status)) AND (is_active IS NOT NULL);

-- pgschema:wait
SELECT
Expand Down
2 changes: 1 addition & 1 deletion testdata/diff/online/add_partial_index/plan.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DDL to be executed:
--------------------------------------------------

-- Transaction Group #1
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_active_orders_customer_date ON orders (customer_id, order_date DESC, total_amount) WHERE (status IN ('pending'::public.order_status, 'processing'::public.order_status, 'confirmed'::public.order_status)) AND (is_active IS NOT NULL);

-- Transaction Group #2
-- pgschema:wait
Expand Down