Skip to content

Implement Arithmetic and Concat Expressions#127

Merged
cungminh2710 merged 2 commits into
mainfrom
feat/arithmetic-and-concat-expressions-12834576655270581647
Jun 18, 2026
Merged

Implement Arithmetic and Concat Expressions#127
cungminh2710 merged 2 commits into
mainfrom
feat/arithmetic-and-concat-expressions-12834576655270581647

Conversation

@cungminh2710

Copy link
Copy Markdown
Contributor

This change introduces basic SQL arithmetic operations and string concatenation to Rain ORM, improving its parity with Drizzle ORM.

Key features:

  • Arithmetic: Typed and untyped columns now support .Add(), .Sub(), .Mul(), .Div(), and .Mod(). These can be chained together.
  • Concatenation: A new schema.Concat() helper provides dialect-aware string concatenation.
  • Correct Rendering: The query compiler wraps arithmetic operations in parentheses to ensure SQL operator precedence is maintained.
  • Subquery Support: New expression types are correctly cloned when tables are aliased.
  • Testing: Comprehensive tests ensure correct SQL generation across dialects and successful execution against SQLite.

PR created automatically by Jules for task 12834576655270581647 started by @cungminh2710

Implement arithmetic operators (+, -, *, /, %) and string concatenation
in the Rain ORM query builder.

- Add BinaryExpr and ConcatExpr to pkg/schema
- Add Add, Sub, Mul, Div, Mod methods to Column[T] and AnyColumn
- Support expression chaining (e.g., col.Add(1).Mul(2))
- Implement dialect-aware concatenation (|| for Postgres/SQLite, CONCAT() for MySQL)
- Support aliasing via .As() on new expression types
- Update query compiler to render expressions with correct precedence
- Add unit and SQLite integration tests

Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds arithmetic (Add, Sub, Mul, Div, Mod) and string concatenation (Concat) expression types to Rain ORM, with dialect-aware SQL rendering and correct operator-precedence parenthesisation. As a bonus, the diff also back-fills an operator allowlist for the pre-existing ComparisonExpr compiler case, closing a latent injection path.

  • schema.go: New BinaryExpr and ConcatExpr types with helpers on both AnyColumn (untyped, accepts any) and Column[T] (typed); cloneExpressionForTable extended for both types.
  • query_compile.go: Compiler cases for BinaryExpr (allowlisted operators, wraps in parentheses) and ConcatExpr (uses || for postgres/sqlite, CONCAT() for mysql); ComparisonExpr now also validates its operator.
  • Tests: Unit tests cover SQL generation across dialects; integration tests execute all five arithmetic operations and Concat against a live SQLite database.

Confidence Score: 5/5

The change is additive and well-scoped; existing query paths are unaffected and new expression types are gated behind operator allowlists in the compiler.

All new code paths are covered by both unit and integration tests. The operator allowlists prevent injection, parenthesisation is correct, and dialect branching is explicit. The only issue found is a stale copy-paste comment in one test; the assertion itself is correct so no test coverage is lost.

No files require special attention; the minor comment discrepancy in sqlite_integration_test.go does not affect correctness.

Important Files Changed

Filename Overview
pkg/schema/schema.go Adds BinaryExpr and ConcatExpr types with full arithmetic/concat helpers on AnyColumn and Column[T], plus cloneExpressionForTable support for the new types.
pkg/rain/query_compile.go Adds compiler cases for BinaryExpr (operator allowlist + parenthesisation) and ConcatExpr (dialect-aware
pkg/rain/expressions_test.go New unit tests for SQL generation of arithmetic and Concat across postgres and mysql dialects; no logic issues.
pkg/rain/sqlite_integration_test.go Integration test for all five arithmetic operations and Concat against live SQLite. One misleading comment (Sub: 10-3=5 should be =7); assertion is correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Column[T].Add/Sub/Mul/Div/Mod(value T)"] --> B[BinaryExpr]
    C["AnyColumn.Add/Sub/Mul/Div/Mod(value any)"] --> B
    B --> D["BinaryExpr.Add/Sub/Mul/Div/Mod(value any) chaining"]
    D --> B
    E["schema.Concat(values ...any)"] --> F[ConcatExpr]
    B --> G{Compiler}
    F --> G
    G -->|BinaryExpr| H{"Operator allowlist + - * / %"}
    H -->|valid| I["( Left op Right )"]
    H -->|invalid| J[return error]
    G -->|ConcatExpr| K{Dialect?}
    K -->|postgres / sqlite| L["( e1 || e2 || ... )"]
    K -->|mysql| M["CONCAT(e1, e2, ...)"]
    K -->|other| N[return error]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["Column[T].Add/Sub/Mul/Div/Mod(value T)"] --> B[BinaryExpr]
    C["AnyColumn.Add/Sub/Mul/Div/Mod(value any)"] --> B
    B --> D["BinaryExpr.Add/Sub/Mul/Div/Mod(value any) chaining"]
    D --> B
    E["schema.Concat(values ...any)"] --> F[ConcatExpr]
    B --> G{Compiler}
    F --> G
    G -->|BinaryExpr| H{"Operator allowlist + - * / %"}
    H -->|valid| I["( Left op Right )"]
    H -->|invalid| J[return error]
    G -->|ConcatExpr| K{Dialect?}
    K -->|postgres / sqlite| L["( e1 || e2 || ... )"]
    K -->|mysql| M["CONCAT(e1, e2, ...)"]
    K -->|other| N[return error]
Loading

Reviews (2): Last reviewed commit: "feat(rain): add support for arithmetic a..." | Re-trigger Greptile

Comment thread pkg/schema/schema.go
Comment on lines 1049 to +1058
func (NullCheckExpr) isExpression() {}
func (NullCheckExpr) isPredicate() {}

// BinaryExpr represents an arithmetic operation.
type BinaryExpr struct {
Left Expression
Operator string
Right Expression
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security Operator field is exported with no validation

BinaryExpr.Operator is a plain string written verbatim into the SQL output in writeExpressionInContext. The built-in factory methods (Add, Sub, etc.) only ever write safe operators, but because the struct and its fields are exported, a caller can construct schema.BinaryExpr{Operator: arbitraryString} and inject arbitrary SQL. The existing ComparisonExpr has the same pattern, so this is consistent — but that means both types carry the same risk. Consider either unexported fields with constructor functions, or a validation allowlist in the compiler.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/schema/schema.go
Line: 1049-1058

Comment:
**`Operator` field is exported with no validation**

`BinaryExpr.Operator` is a plain `string` written verbatim into the SQL output in `writeExpressionInContext`. The built-in factory methods (`Add`, `Sub`, etc.) only ever write safe operators, but because the struct and its fields are exported, a caller can construct `schema.BinaryExpr{Operator: arbitraryString}` and inject arbitrary SQL. The existing `ComparisonExpr` has the same pattern, so this is consistent — but that means both types carry the same risk. Consider either unexported fields with constructor functions, or a validation allowlist in the compiler.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Comment thread pkg/rain/sqlite_integration_test.go Outdated
@cungminh2710

Copy link
Copy Markdown
Contributor Author

@jules Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.


Issue 1 of 3

pkg/rain/sqlite_integration_test.go:1988-2010
ErrNoRows and RejectSlice subtests misplaced into new function

These two subtests (ErrNoRows and RejectSlice) previously lived inside TestSQLiteIntegrationFirst and specifically test the First() API surface. By inserting +} to close TestSQLiteIntegrationFirst right after the ScanFirst subtest, the diff accidentally moved these subtests into TestSQLiteIntegrationArithmeticAndConcat. TestSQLiteIntegrationFirst now only tests the ScanFirst path, silently dropping coverage of the "no rows" and "reject slice destination" behaviours from their intended test function.

Issue 2 of 3

pkg/schema/schema.go:1049-1058
Operator field is exported with no validation

BinaryExpr.Operator is a plain string written verbatim into the SQL output in writeExpressionInContext. The built-in factory methods (Add, Sub, etc.) only ever write safe operators, but because the struct and its fields are exported, a caller can construct schema.BinaryExpr{Operator: arbitraryString} and inject arbitrary SQL. The existing ComparisonExpr has the same pattern, so this is consistent — but that means both types carry the same risk. Consider either unexported fields with constructor functions, or a validation allowlist in the compiler.

Issue 3 of 3

pkg/rain/sqlite_integration_test.go:1955-1969
Integration test only exercises Add and Add+Mul

The integration test covers Add and a chained Add().Mul() against a live SQLite database, but Sub, Div, and Mod are only tested at the SQL-generation level in expressions_test.go without execution. A dialect-specific edge case (e.g. integer division truncation, modulo sign behaviour, division by zero) would not be caught by the current test suite.

@google-labs-jules

Copy link
Copy Markdown
Contributor

@jules Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.


Issue 1 of 3

pkg/rain/sqlite_integration_test.go:1988-2010
ErrNoRows and RejectSlice subtests misplaced into new function

These two subtests (ErrNoRows and RejectSlice) previously lived inside TestSQLiteIntegrationFirst and specifically test the First() API surface. By inserting +} to close TestSQLiteIntegrationFirst right after the ScanFirst subtest, the diff accidentally moved these subtests into TestSQLiteIntegrationArithmeticAndConcat. TestSQLiteIntegrationFirst now only tests the ScanFirst path, silently dropping coverage of the "no rows" and "reject slice destination" behaviours from their intended test function.

Issue 2 of 3

pkg/schema/schema.go:1049-1058
Operator field is exported with no validation

BinaryExpr.Operator is a plain string written verbatim into the SQL output in writeExpressionInContext. The built-in factory methods (Add, Sub, etc.) only ever write safe operators, but because the struct and its fields are exported, a caller can construct schema.BinaryExpr{Operator: arbitraryString} and inject arbitrary SQL. The existing ComparisonExpr has the same pattern, so this is consistent — but that means both types carry the same risk. Consider either unexported fields with constructor functions, or a validation allowlist in the compiler.

Issue 3 of 3

pkg/rain/sqlite_integration_test.go:1955-1969
Integration test only exercises Add and Add+Mul

The integration test covers Add and a chained Add().Mul() against a live SQLite database, but Sub, Div, and Mod are only tested at the SQL-generation level in expressions_test.go without execution. A dialect-specific edge case (e.g. integer division truncation, modulo sign behaviour, division by zero) would not be caught by the current test suite.

@jules I've addressed the feedback by making the following changes:

  1. Fixed misplaced tests: ErrNoRows and RejectSlice subtests have been moved back into TestSQLiteIntegrationFirst in pkg/rain/sqlite_integration_test.go.
  2. Added operator validation: Updated pkg/rain/query_compile.go to include a validation allowlist for both ComparisonExpr and BinaryExpr operators, preventing arbitrary SQL injection through exported fields.
  3. Expanded integration tests: Updated TestSQLiteIntegrationArithmeticAndConcat to cover Sub, Mul, Div, and Mod against a live SQLite database, ensuring correct dialect-specific behavior.

All tests passed successfully.

Implement arithmetic operators (+, -, *, /, %) and string concatenation
in the Rain ORM query builder.

- Add BinaryExpr and ConcatExpr to pkg/schema
- Add Add, Sub, Mul, Div, Mod methods to Column[T] and AnyColumn
- Support expression chaining (e.g., col.Add(1).Mul(2))
- Implement dialect-aware concatenation (|| for Postgres/SQLite, CONCAT() for MySQL)
- Support aliasing via .As() on new expression types
- Update query compiler to render expressions with correct precedence
- Add operator validation allowlist to prevent SQL injection
- Add unit and SQLite integration tests covering all operators

Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
@cungminh2710 cungminh2710 merged commit 07228a5 into main Jun 18, 2026
5 checks passed
@cungminh2710 cungminh2710 deleted the feat/arithmetic-and-concat-expressions-12834576655270581647 branch June 18, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant