Skip to content

ORM: dynamic select with multiple conditions causes "column index out of range" error when using IN operator #26913

@Jengro777

Description

@Jengro777

Describe the bug

When using dynamic select with both IN operator and other conditions (e.g., age >= ?), the ORM generates a SQL query that causes a "column index out of range" error.

Reproduction Steps

// vtest retry: 3
import db.sqlite

@[table: 'dynamic_members']
struct DynamicMember {
mut:
	id     int @[primary; sql: serial]
	name   string
	email  string
	age    int
	status string
}

fn dynamic_select_with_in_operator() ! {
	mut db := sqlite.connect(':memory:')!
	defer {
		db.close() or { panic(err) }
	}

	sql db {
		create table DynamicMember
	}!

	members := [
		DynamicMember{ name: 'Alice', email: 'alice@example.com', age: 31, status: 'active' },
		DynamicMember{ name: 'Bob', email: 'bob@example.com', age: 24, status: 'pending' },
		DynamicMember{ name: 'Charlie', email: 'charlie@example.com', age: 29, status: 'active' },
		DynamicMember{ name: 'Diana', email: 'diana@example.com', age: 35, status: 'inactive' },
		DynamicMember{ name: 'Eve', email: 'eve@example.com', age: 22, status: 'pending' },
	]

	for member in members {
		sql db {
			insert member into DynamicMember
		}!
	}

	// 1: IN 
	status_list := ['active', 'pending']
	rows1 := sql db {
		dynamic select from DynamicMember where {
			if status_list.len > 0 {
				status in status_list
			}
		} order by id
	}!

	println('IN test (status in [active, pending]): ${rows1.len} rows')
	assert rows1.len == 4  // Alice(active), Bob(pending), Charlie(active), Eve(pending)
	for row in rows1 {
		println('  - ${row.name}: ${row.status}')
		assert row.status in status_list
	}

	//  2: !IN 
	exclude_status := ['inactive']
	rows2 := sql db {
		dynamic select from DynamicMember where {
			if exclude_status.len > 0 {
				status !in exclude_status
			}
		} order by id
	}!

	println('!IN test (status not in [inactive]): ${rows2.len} rows')
	assert rows2.len == 4  // 排除 Diana(inactive)
	for row in rows2 {
		println('  - ${row.name}: ${row.status}')
		assert row.status !in exclude_status
	}

	// 3:  name IN + status 
	valid_names := ['Alice', 'Charlie', 'Eve']
	min_age := 25

	rows3 := sql db {
		dynamic select from DynamicMember where {
			if valid_names.len > 0 {
				name in valid_names
			},
			if min_age > 0 {
				age >= min_age
			}
		} order by id
	}!

	println('Combined test (name in [Alice,Charlie,Eve] AND age >= 25): ${rows3.len} rows')
	assert rows3.len == 2  // Alice(31), Charlie(29)
	for row in rows3 {
		println('  - ${row.name}: age ${row.age}')
		assert row.name in valid_names
		assert row.age >= min_age
	}

	println('All IN/!IN tests passed!')
}

fn dynamic_update_with_in_condition() ! {
	mut db := sqlite.connect(':memory:')!
	defer {
		db.close() or { panic(err) }
	}

	sql db {
		create table DynamicMember
	}!

	initial_members := [
		DynamicMember{ name: 'Alice', email: 'alice@example.com', age: 31, status: 'pending' },
		DynamicMember{ name: 'Bob', email: 'bob@example.com', age: 24, status: 'pending' },
		DynamicMember{ name: 'Charlie', email: 'charlie@example.com', age: 29, status: 'active' },
	]

	for member in initial_members {
		sql db {
			insert member into DynamicMember
		}!
	}

	//  status IN [pending] 
	pending_statuses := ['pending']
	new_status := 'approved'

	
	before_rows := sql db {
		select from DynamicMember where status in pending_statuses
	}!
	println('Before update: ${before_rows.len} pending records')


	update_expr := {
		status == new_status
	}

	sql db {
		dynamic update DynamicMember set update_expr where {
			if pending_statuses.len > 0 {
				status in pending_statuses
			}
		}
	}!


	after_rows := sql db {
		select from DynamicMember where status == new_status
	}!
	println('After update: ${after_rows.len} records updated to ${new_status}')
	assert after_rows.len == before_rows.len

	for row in after_rows {
		println('  - ${row.name}: ${row.status}')
		assert row.status == new_status
	}


	active_rows := sql db {
		select from DynamicMember where name == 'Charlie'
	}!
	assert active_rows[0].status == 'active'
	println('Charlie remains ${active_rows[0].status} (correct)')

	println('Dynamic update with IN condition test passed!')
}

fn main() {
	println('=== Testing IN and !IN operators ===')
	println('')

	dynamic_select_with_in_operator() or {
		println('Error in select tests: ${err}')
		return
	}

	println('')

	dynamic_update_with_in_condition() or {
		println('Error in update test: ${err}')
		return
	}

	println('')
	println('✅ All IN/!IN tests passed successfully!')
}

Expected Behavior

The query should execute successfully, returning records that match both conditions (name IN (...) AND age >= ...).

Current Behavior

column index out of range (25) (SELECT id, name, email, age, status FROM dynamic_members WHERE name IN (?1, ?2, ?3) AND age >= ?4 ORDER BY id ASC;); code: 25

Possible Solution

No response

Additional Information/Context

No response

V version

V 0.5.1 1b3385c

Environment details (OS name and version, etc.)

V full version V 0.5.1 1b3385c
OS macos, macOS, 13.2.1, 22D68
Processor 8 cpus, 64bit, little endian, Intel(R) Core(TM) i5-8257U CPU @ 1.40GHz
Memory 0.2GB/8GB
V executable /Applications/v/v
V last modified time 2026-04-18 02:43:18
V home dir OK, value: /Applications/v
VMODULES OK, value: /Users/avey/.vmodules
VTMP OK, value: /tmp/v_501
Current working dir OK, value: /Users/avey/Documents/Dev/RuoQi/RuoQi-v/temp
env LDFLAGS "-L/usr/local/opt/openssl@3/lib"
Git version git version 2.50.0
V git status 0.5.1-1005-g1b3385cc
.git/config present true
cc version Apple clang version 14.0.3 (clang-1403.0.22.14.1)
gcc version Apple clang version 14.0.3 (clang-1403.0.22.14.1)
clang version Apple clang version 14.0.3 (clang-1403.0.22.14.1)
tcc version tcc version 0.9.27 (x86_64 Darwin)
tcc git status thirdparty-macos-amd64 975f1ad8
emcc version N/A
glibc version N/A

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugThis tag is applied to issues which reports bugs.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions