or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdconnection-pooling.mdconnections-cursors.mdcursors-rows.mderror-handling.mdindex.mdsql-composition.mdtypes-adaptation.md

sql-composition.mddocs/

0

# SQL Composition and Query Building

1

2

Safe SQL query construction using composable objects for identifiers, literals, and SQL fragments. The sql module prevents SQL injection while enabling dynamic query building through a flexible composition system that properly handles identifiers, literals, and SQL fragments.

3

4

## Capabilities

5

6

### Composable Base Classes

7

8

Foundation classes for building SQL queries through composition with proper escaping and formatting.

9

10

```python { .api }

11

class Composable:

12

"""Abstract base class for SQL composable objects."""

13

14

def __init__(self, wrapped):

15

"""Initialize composable with wrapped content."""

16

17

def as_string(self, context):

18

"""

19

Return string representation for SQL context.

20

21

Parameters:

22

- context (connection/cursor): SQL context for rendering

23

24

Returns:

25

str: SQL string representation

26

"""

27

28

def __add__(self, other):

29

"""

30

Concatenate with another composable.

31

32

Parameters:

33

- other (Composable): Object to concatenate

34

35

Returns:

36

Composed: Combined composable object

37

"""

38

39

def __mul__(self, n):

40

"""

41

Repeat composable n times.

42

43

Parameters:

44

- n (int): Number of repetitions

45

46

Returns:

47

Composed: Repeated composable object

48

"""

49

50

class Composed(Composable):

51

"""Sequence of composable objects."""

52

53

def __init__(self, seq):

54

"""

55

Initialize from sequence of composables.

56

57

Parameters:

58

- seq (sequence): Sequence of Composable objects

59

"""

60

61

@property

62

def seq(self):

63

"""

64

Access to composable sequence.

65

66

Returns:

67

list: List of composable objects

68

"""

69

70

def join(self, joiner):

71

"""

72

Join composables with separator.

73

74

Parameters:

75

- joiner (str/SQL): Separator string or SQL object

76

77

Returns:

78

Composed: Joined composable object

79

"""

80

```

81

82

### SQL Fragments

83

84

Raw SQL string fragments that are not escaped, for static SQL content.

85

86

```python { .api }

87

class SQL(Composable):

88

"""SQL string fragment (not escaped)."""

89

90

def __init__(self, string):

91

"""

92

Initialize SQL fragment.

93

94

Parameters:

95

- string (str): SQL string content

96

97

Raises:

98

TypeError: If string is not str type

99

"""

100

101

@property

102

def string(self):

103

"""

104

Access to wrapped string.

105

106

Returns:

107

str: SQL string content

108

"""

109

110

def format(self, *args, **kwargs):

111

"""

112

Format SQL with composable arguments.

113

114

Parameters:

115

- *args: Positional composable arguments

116

- **kwargs: Named composable arguments

117

118

Returns:

119

Composed: Formatted SQL object

120

"""

121

122

def join(self, seq):

123

"""

124

Join sequence with this SQL as separator.

125

126

Parameters:

127

- seq (iterable): Sequence of composables

128

129

Returns:

130

Composed: Joined composable object

131

"""

132

```

133

134

Usage examples:

135

136

```python

137

from psycopg2 import sql

138

139

# Basic SQL fragments

140

select_sql = sql.SQL("SELECT * FROM users")

141

where_sql = sql.SQL("WHERE active = %s")

142

143

# Combining SQL fragments

144

query = select_sql + sql.SQL(" ") + where_sql

145

print(query.as_string(conn)) # "SELECT * FROM users WHERE active = %s"

146

147

# SQL formatting with placeholders

148

template = sql.SQL("SELECT {fields} FROM {table}")

149

query = template.format(

150

fields=sql.SQL("id, name, email"),

151

table=sql.SQL("users")

152

)

153

154

# Joining SQL fragments

155

fields = [sql.SQL("id"), sql.SQL("name"), sql.SQL("email")]

156

field_list = sql.SQL(", ").join(fields)

157

```

158

159

### Identifiers

160

161

SQL identifiers (table names, column names) with proper quoting and escaping.

162

163

```python { .api }

164

class Identifier(Composable):

165

"""SQL identifier with proper quoting."""

166

167

def __init__(self, *strings):

168

"""

169

Initialize identifier.

170

171

Parameters:

172

- *strings: Identifier components (e.g., schema, table, column)

173

"""

174

175

@property

176

def strings(self):

177

"""

178

Access to identifier components.

179

180

Returns:

181

tuple: Identifier string components

182

"""

183

```

184

185

Usage examples:

186

187

```python

188

from psycopg2 import sql

189

190

# Simple identifiers

191

table = sql.Identifier('users')

192

column = sql.Identifier('user_name')

193

194

# Qualified identifiers (schema.table, table.column)

195

qualified_table = sql.Identifier('public', 'users')

196

qualified_column = sql.Identifier('users', 'id')

197

198

# Safe dynamic table/column selection

199

def get_user_data(table_name, column_name):

200

query = sql.SQL("SELECT {column} FROM {table}").format(

201

column=sql.Identifier(column_name),

202

table=sql.Identifier(table_name)

203

)

204

return query

205

206

# Usage - properly quoted regardless of input

207

query = get_user_data('user_profiles', 'first_name')

208

# Result: SELECT "first_name" FROM "user_profiles"

209

210

query = get_user_data('special-table', 'column with spaces')

211

# Result: SELECT "column with spaces" FROM "special-table"

212

```

213

214

### Literals

215

216

SQL literal values with proper escaping and type conversion.

217

218

```python { .api }

219

class Literal(Composable):

220

"""SQL literal value with proper escaping."""

221

222

def __init__(self, wrapped):

223

"""

224

Initialize literal value.

225

226

Parameters:

227

- wrapped: Python value to convert to SQL literal

228

"""

229

230

@property

231

def wrapped(self):

232

"""

233

Access to wrapped value.

234

235

Returns:

236

Wrapped Python value

237

"""

238

```

239

240

Usage examples:

241

242

```python

243

from psycopg2 import sql

244

245

# Basic literals

246

name_literal = sql.Literal('Alice')

247

age_literal = sql.Literal(30)

248

active_literal = sql.Literal(True)

249

250

# Dynamic WHERE clauses

251

def build_where_clause(conditions):

252

clauses = []

253

for column, value in conditions.items():

254

clause = sql.SQL("{} = {}").format(

255

sql.Identifier(column),

256

sql.Literal(value)

257

)

258

clauses.append(clause)

259

return sql.SQL(" AND ").join(clauses)

260

261

# Usage

262

conditions = {'name': 'Alice', 'age': 30, 'active': True}

263

where_clause = build_where_clause(conditions)

264

# Result: "name" = 'Alice' AND "age" = 30 AND "active" = true

265

266

query = sql.SQL("SELECT * FROM users WHERE {}").format(where_clause)

267

```

268

269

### Placeholders

270

271

SQL parameter placeholders for prepared statement usage.

272

273

```python { .api }

274

class Placeholder(Composable):

275

"""SQL parameter placeholder."""

276

277

def __init__(self, name=None):

278

"""

279

Initialize placeholder.

280

281

Parameters:

282

- name (str, optional): Named parameter name

283

"""

284

285

@property

286

def name(self):

287

"""

288

Access to placeholder name.

289

290

Returns:

291

str/None: Parameter name or None for positional

292

"""

293

```

294

295

Usage examples:

296

297

```python

298

from psycopg2 import sql

299

300

# Named placeholders

301

name_placeholder = sql.Placeholder('name')

302

age_placeholder = sql.Placeholder('age')

303

304

query = sql.SQL("SELECT * FROM users WHERE name = {} AND age > {}").format(

305

name_placeholder, age_placeholder

306

)

307

# Result: "SELECT * FROM users WHERE name = %(name)s AND age > %(age)s"

308

309

# Execute with named parameters

310

cur.execute(query, {'name': 'Alice', 'age': 25})

311

312

# Positional placeholders

313

pos_placeholder = sql.Placeholder()

314

query = sql.SQL("INSERT INTO users (name, email) VALUES ({}, {})").format(

315

pos_placeholder, pos_placeholder

316

)

317

# Result: "INSERT INTO users (name, email) VALUES (%s, %s)"

318

319

cur.execute(query, ('Alice', 'alice@example.com'))

320

```

321

322

### Advanced Query Building

323

324

Complex query construction patterns using composition methods.

325

326

```python

327

# Dynamic SELECT with optional fields

328

def build_select_query(table, fields=None, conditions=None, order_by=None):

329

# Base query

330

if fields:

331

field_list = sql.SQL(", ").join(sql.Identifier(f) for f in fields)

332

else:

333

field_list = sql.SQL("*")

334

335

query = sql.SQL("SELECT {} FROM {}").format(

336

field_list,

337

sql.Identifier(table)

338

)

339

340

# Add WHERE clause

341

if conditions:

342

where_parts = []

343

for column, value in conditions.items():

344

where_parts.append(

345

sql.SQL("{} = {}").format(

346

sql.Identifier(column),

347

sql.Placeholder(column)

348

)

349

)

350

where_clause = sql.SQL(" AND ").join(where_parts)

351

query = query + sql.SQL(" WHERE ") + where_clause

352

353

# Add ORDER BY

354

if order_by:

355

order_fields = sql.SQL(", ").join(

356

sql.Identifier(field) for field in order_by

357

)

358

query = query + sql.SQL(" ORDER BY ") + order_fields

359

360

return query

361

362

# Usage

363

query = build_select_query(

364

table='users',

365

fields=['id', 'name', 'email'],

366

conditions={'active': True, 'role': 'admin'},

367

order_by=['name', 'created_at']

368

)

369

370

cur.execute(query, {'active': True, 'role': 'admin'})

371

```

372

373

### Bulk Operations with SQL Composition

374

375

Using SQL composition with bulk operations for dynamic queries.

376

377

```python

378

from psycopg2.extras import execute_values

379

380

# Dynamic bulk insert

381

def bulk_insert(table, columns, data):

382

# Build column list

383

column_list = sql.SQL(", ").join(sql.Identifier(col) for col in columns)

384

385

# Build VALUES template

386

value_template = sql.SQL("({})").format(

387

sql.SQL(", ").join(sql.Placeholder() for _ in columns)

388

)

389

390

# Complete query

391

query = sql.SQL("INSERT INTO {} ({}) VALUES {}").format(

392

sql.Identifier(table),

393

column_list,

394

value_template

395

)

396

397

# Execute bulk insert

398

execute_values(cur, query, data, template=None, page_size=100)

399

400

# Usage

401

columns = ['name', 'email', 'age']

402

data = [

403

('Alice', 'alice@example.com', 30),

404

('Bob', 'bob@example.com', 25),

405

('Charlie', 'charlie@example.com', 35)

406

]

407

408

bulk_insert('users', columns, data)

409

```

410

411

### SQL Injection Prevention

412

413

SQL composition provides strong protection against SQL injection attacks.

414

415

```python

416

# UNSAFE - vulnerable to SQL injection

417

user_input = "'; DROP TABLE users; --"

418

unsafe_query = f"SELECT * FROM users WHERE name = '{user_input}'"

419

# DON'T DO THIS!

420

421

# SAFE - using SQL composition

422

safe_query = sql.SQL("SELECT * FROM users WHERE name = {}").format(

423

sql.Literal(user_input)

424

)

425

# Properly escaped: SELECT * FROM users WHERE name = '''; DROP TABLE users; --'

426

427

# SAFE - using placeholders (preferred for values)

428

param_query = sql.SQL("SELECT * FROM users WHERE name = {}").format(

429

sql.Placeholder('name')

430

)

431

cur.execute(param_query, {'name': user_input})

432

433

# SAFE - identifiers properly quoted

434

table_name = "user-profiles" # Contains special characters

435

safe_table_query = sql.SQL("SELECT * FROM {}").format(

436

sql.Identifier(table_name)

437

)

438

# Result: SELECT * FROM "user-profiles"

439

```

440

441

## Types

442

443

### Composition Operators

444

445

```python { .api }

446

# Concatenation

447

composable1 + composable2 # Returns Composed object

448

449

# Repetition

450

composable * n # Returns Composed with n repetitions

451

452

# Equality

453

composable1 == composable2 # True if same type and content

454

```

455

456

### Context Objects

457

458

```python { .api }

459

SqlContext = connection | cursor # Valid contexts for as_string()

460

```

461

462

### String Formatting

463

464

```python { .api }

465

# Format placeholders

466

"{}" # Positional placeholder

467

"{0}", "{1}" # Numbered placeholders

468

"{name}" # Named placeholder

469

470

# Format arguments must be Composable objects

471

FormatArgs = Composable | tuple[Composable, ...] | dict[str, Composable]

472

```

473

474

### Common Patterns

475

476

```python

477

# Table and column name building

478

schema_table = sql.Identifier('schema_name', 'table_name')

479

table_column = sql.Identifier('table_name', 'column_name')

480

481

# IN clause construction

482

values = [sql.Literal(v) for v in [1, 2, 3, 4]]

483

in_clause = sql.SQL("IN ({})").format(sql.SQL(", ").join(values))

484

485

# Dynamic ORDER BY

486

order_fields = ['name', 'created_at']

487

order_clause = sql.SQL("ORDER BY {}").format(

488

sql.SQL(", ").join(sql.Identifier(f) for f in order_fields)

489

)

490

491

# CASE expressions

492

case_expr = sql.SQL("""

493

CASE

494

WHEN {} > {} THEN {}

495

ELSE {}

496

END

497

""").format(

498

sql.Identifier('age'),

499

sql.Literal(18),

500

sql.Literal('adult'),

501

sql.Literal('minor')

502

)

503

```