Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add ClickHouse Engine Support to sqlc#4220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
mgilbir wants to merge13 commits intosqlc-dev:main
base:main
Choose a base branch
Loading
frommgilbir:add-clickhouse-support
Open
Show file tree
Hide file tree
Changes from1 commit
Commits
Show all changes
13 commits
Select commitHold shift + click to select a range
f30b317
Add ClickHouse engine: parser, converter, and catalog
mgilbirDec 6, 2025
7960b17
Add ClickHouse engine unit tests
mgilbirDec 6, 2025
5112bab
Register ClickHouse engine in compiler
mgilbirDec 6, 2025
e817d54
Add ClickHouse type mapping for Go code generation
mgilbirDec 6, 2025
d426a16
Add ClickHouse code generation templates
mgilbirDec 6, 2025
14e9917
Add JOIN USING support and refactor output column handling
mgilbirDec 6, 2025
cec1ed9
Add ClickHouse test database adapters
mgilbirDec 6, 2025
15d203a
Add ClickHouse documentation and example project
mgilbirDec 6, 2025
cc9759f
Add ClickHouse example project generated code
mgilbirDec 6, 2025
cfdb3ff
Add end-to-end test for JOIN...USING syntax
mgilbirDec 6, 2025
d7965d6
Add end-to-end tests for ClickHouse core SQL features
mgilbirDec 6, 2025
01638c9
Add end-to-end tests for ClickHouse advanced SQL features
mgilbirDec 6, 2025
00ce8b7
Add end-to-end tests for ClickHouse types and functions
mgilbirDec 6, 2025
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
PrevPrevious commit
NextNext commit
Add JOIN USING support and refactor output column handling
Implement JOIN...USING clause support for ClickHouse and PostgreSQL.Refactor output_columns.go for improved type resolution.Add comprehensive tests for output columns and type resolution.Update quote character handling and catalog interface.
  • Loading branch information
@mgilbir
mgilbir committedDec 6, 2025
commit14e9917e306f423d196ab04b1f8c7e1ef0c7c3ee
196 changes: 196 additions & 0 deletionsinternal/compiler/clickhouse_join_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
package compiler

import (
"strings"
"testing"

"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/engine/clickhouse"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)

// TestClickHouseJoinColumnResolution tests that column names are properly resolved
// in JOIN queries now that JoinExpr is correctly converted
func TestClickHouseJoinColumnResolution(t *testing.T) {
parser := clickhouse.NewParser()
cat := clickhouse.NewCatalog()

// Create database and tables
schemaSQL := `CREATE DATABASE IF NOT EXISTS test_db;
CREATE TABLE test_db.users (
id UInt32,
name String,
email String
);
CREATE TABLE test_db.posts (
id UInt32,
user_id UInt32,
title String,
content String
)`

stmts, err := parser.Parse(strings.NewReader(schemaSQL))
if err != nil {
t.Fatalf("Parse schema failed: %v", err)
}

for _, stmt := range stmts {
if err := cat.Update(stmt, nil); err != nil {
t.Fatalf("Update catalog failed: %v", err)
}
}

// Create compiler
conf := config.SQL{
Engine: config.EngineClickHouse,
}
combo := config.CombinedSettings{
Global: config.Config{},
}

c, err := NewCompiler(conf, combo)
if err != nil {
t.Fatalf("Failed to create compiler: %v", err)
}

// Replace catalog
c.catalog = cat

// Parse a JOIN query
querySQL := "SELECT u.id, u.name, p.id as post_id, p.title FROM test_db.users u LEFT JOIN test_db.posts p ON u.id = p.user_id WHERE u.id = 1"
queryStmts, err := parser.Parse(strings.NewReader(querySQL))
if err != nil {
t.Fatalf("Parse query failed: %v", err)
}

if len(queryStmts) == 0 {
t.Fatal("No queries parsed")
}

selectStmt := queryStmts[0].Raw.Stmt
if selectStmt == nil {
t.Fatal("Select statement is nil")
}

selectAst, ok := selectStmt.(*ast.SelectStmt)
if !ok {
t.Fatalf("Expected SelectStmt, got %T", selectStmt)
}

// Build query catalog and get output columns
qc, err := c.buildQueryCatalog(c.catalog, selectAst, nil)
if err != nil {
t.Fatalf("Failed to build query catalog: %v", err)
}

cols, err := c.outputColumns(qc, selectAst)
if err != nil {
t.Fatalf("Failed to get output columns: %v", err)
}

if len(cols) != 4 {
t.Errorf("Expected 4 columns, got %d", len(cols))
}

expectedNames := []string{"id", "name", "post_id", "title"}
for i, expected := range expectedNames {
if i < len(cols) {
if cols[i].Name != expected {
t.Errorf("Column %d: expected name %q, got %q", i, expected, cols[i].Name)
}
}
}
}

// TestClickHouseLeftJoinNullability tests that LEFT JOIN correctly marks right-side columns as nullable
// In ClickHouse, columns are non-nullable by default unless wrapped in Nullable(T)
func TestClickHouseLeftJoinNullability(t *testing.T) {
parser := clickhouse.NewParser()
cat := clickhouse.NewCatalog()

schemaSQL := `CREATE TABLE orders (
order_id UInt32,
customer_name String,
amount Float64,
created_at DateTime
);
CREATE TABLE shipments (
shipment_id UInt32,
order_id UInt32,
address String,
shipped_at DateTime
)`

stmts, err := parser.Parse(strings.NewReader(schemaSQL))
if err != nil {
t.Fatalf("Parse schema failed: %v", err)
}

for _, stmt := range stmts {
if err := cat.Update(stmt, nil); err != nil {
t.Fatalf("Update catalog failed: %v", err)
}
}

conf := config.SQL{
Engine: config.EngineClickHouse,
}
combo := config.CombinedSettings{
Global: config.Config{},
}

c, err := NewCompiler(conf, combo)
if err != nil {
t.Fatalf("Failed to create compiler: %v", err)
}
c.catalog = cat

querySQL := "SELECT o.order_id, o.customer_name, o.amount, o.created_at, s.shipment_id, s.address, s.shipped_at FROM orders o LEFT JOIN shipments s ON o.order_id = s.order_id ORDER BY o.created_at DESC"
queryStmts, err := parser.Parse(strings.NewReader(querySQL))
if err != nil {
t.Fatalf("Parse query failed: %v", err)
}

selectAst := queryStmts[0].Raw.Stmt.(*ast.SelectStmt)
qc, err := c.buildQueryCatalog(c.catalog, selectAst, nil)
if err != nil {
t.Fatalf("Failed to build query catalog: %v", err)
}

cols, err := c.outputColumns(qc, selectAst)
if err != nil {
t.Fatalf("Failed to get output columns: %v", err)
}

if len(cols) != 7 {
t.Errorf("Expected 7 columns, got %d", len(cols))
}

// Left table columns should be non-nullable
leftTableNonNull := map[string]bool{
"order_id": true,
"customer_name": true,
"amount": true,
"created_at": true,
}

// Right table columns should be nullable (because of LEFT JOIN)
rightTableNullable := map[string]bool{
"shipment_id": true,
"address": true,
"shipped_at": true,
}

for _, col := range cols {
if expected, ok := leftTableNonNull[col.Name]; ok {
if col.NotNull != expected {
t.Errorf("Column %q: expected NotNull=%v, got %v", col.Name, expected, col.NotNull)
}
}
if expected, ok := rightTableNullable[col.Name]; ok {
if col.NotNull == expected {
t.Errorf("Column %q: expected NotNull=%v, got %v", col.Name, !expected, col.NotNull)
}
}
}
}
20 changes: 18 additions & 2 deletionsinternal/compiler/expand.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,6 +71,8 @@ func (c *Compiler) quoteIdent(ident string) string {

func (c *Compiler) quote(x string) string {
switch c.conf.Engine {
case config.EngineClickHouse:
return "`" + x + "`"
case config.EngineMySQL:
return "`" + x + "`"
default:
Expand All@@ -84,6 +86,9 @@ func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node)
return nil, err
}

// Track USING columns to avoid duplicating them in SELECT * expansion
usingMap := getJoinUsingMap(node)

var targets *ast.List
switch n := node.(type) {
case *ast.DeleteStmt:
Expand DownExpand Up@@ -126,8 +131,13 @@ func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node)
counts := map[string]int{}
if scope == "" {
for _, t := range tables {
for _, c := range t.Columns {
counts[c.Name] += 1
for _, col := range t.Columns {
// Don't count columns that are in USING clause for this table
// since they won't be included in the expansion
if usingInfo, ok := usingMap[t.Rel.Name]; ok && usingInfo.HasColumn(col.Name) {
continue
}
counts[col.Name] += 1
}
}
}
Expand All@@ -138,6 +148,12 @@ func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node)
tableName := c.quoteIdent(t.Rel.Name)
scopeName := c.quoteIdent(scope)
for _, column := range t.Columns {
// Skip columns that are in USING clause for this table
// to avoid duplication (USING naturally returns only one column)
if usingInfo, ok := usingMap[t.Rel.Name]; ok && usingInfo.HasColumn(column.Name) {
continue
}

cname := column.Name
if res.Name != nil {
cname = *res.Name
Expand Down
Loading

[8]ページ先頭

©2009-2026 Movatter.jp