import { Meta } from "@storybook/addon-docs";
import { QueryPreview } from "./QueryPreview";

<Meta title="2. Conditions" />

# Condition Builder

The Condition Builder provides a set of utilities to construct SQL conditions. It abstracts the raw SQL syntax, allowing developers to build conditions in a more readable and maintainable manner.

## Basic Usage

To start building a condition, use the `Conditions` object:

<QueryPreview
  code={`
Cond.equal('columnName', 'foo');
`}
/>

## Condition Types

### EQUAL

To check if a column is equal to a value:

<QueryPreview
  code={`
Cond.equal('foo', 123);
`}
/>

### NOT EQUAL

To check if a column is not equal to a value:

<QueryPreview
  code={`
Cond.notEqual('foo', '123');
`}
/>

### GREATER THAN

To check if a column's value is greater than a given value:

<QueryPreview
  code={`
Cond.greaterThan('foo', 123);
`}
/>

### LESS THAN

To check if a column's value is less than a given value:

<QueryPreview
  code={`
Cond.lessThan('foo', '123');
`}
/>

### GREATER THAN OR EQUAL

To check if a column's value is greater than or equal to a given value:

<QueryPreview
  code={`
Cond.greaterThanOrEqual('foo', 123);
`}
/>

### LESS THAN OR EQUAL

To check if a column's value is less than or equal to a given value:

<QueryPreview
  code={`
Cond.lessThanOrEqual('foo', '123');
`}
/>

### BETWEEN

To check if a column's value is between two values:

<QueryPreview
  code={`
Cond.between('foo', [1, 2]);
`}
/>

You can also use dates:

<QueryPreview
  code={`
Cond.between('foo', [dayjs().startOf('year'), dayjs().endOf('day')]);
`}
/>

Note: outputing SQL depends on [timezone of flavor](/docs/4-dates-and-timezones--docs).

### IN

To check if a column's value is within a set of values:

<QueryPreview
  code={`
Cond.in('foo', [1, 2, 3]);
`}
/>

#### IN with a subquery

`Cond.in` also accepts a `SelectQuery` as its value, generating a `IN (SELECT …)`
subquery. The subquery is fully serializable, so it survives a
`serialize`/`deserialize` round-trip identically to its direct `toSQL` output:

<QueryPreview
  code={`
// id IN (SELECT user_id FROM orders WHERE active = 1)
Cond.in('id', Q.select().field('user_id').from('orders').where(Cond.equal('active', 1)));
`}
/>

#### Empty IN is fail-closed

An empty (or \`null\`) value list renders \`IN (NULL)\` — a condition that never
matches — instead of being dropped. This is intentional: an empty \`IN\` must
never silently mean "no filter", which would expose every row. Because it is a
real condition, it also survives serialization:

<QueryPreview
  code={`
// foo IN (NULL)  — never matches, never silently dropped
Cond.in('foo', []);
`}
/>

### NOT IN

To check if a column's value is **not** within a set of values:

<QueryPreview
  code={`
Cond.notIn('foo', [1, 2, 3]);
`}
/>

`Cond.notIn` mirrors `Cond.in`: it accepts a subquery and an empty list is
fail-closed as `NOT IN (NULL)`.

<QueryPreview
  code={`
// id NOT IN (SELECT user_id FROM orders WHERE blocked = 1)
Cond.notIn('id', Q.select().field('user_id').from('orders').where(Cond.equal('blocked', 1)));
`}
/>

### AND

To combine multiple conditions with an AND operator:

<QueryPreview
  code={`
Cond.and([
  Cond.equal('foo', 123),
  Cond.greaterThan('bar', 50),
]);
`}
/>

### OR

To combine multiple conditions with an OR operator:

<QueryPreview
  code={`
Cond.or([Cond.equal('foo', 123), Cond.lessThan('bar', 50)]);
`}
/>

### Combining AND/OR

To combine various AND/OR conditions together:

<QueryPreview
  code={`
Cond.and([
      Cond.between('price', [10, 50]),
      Cond.or([
        Cond.like('name', '%apple%'),
        Cond.notLike('description', '%refurbished%'),
      ]),
    ])
`}
/>

### NULL

To check if a column's value is NULL:

<QueryPreview
  code={`
Cond.null('foo');
`}
/>

### NOT NULL

To check if a column's value is NOT NULL:

<QueryPreview
  code={`
Cond.notNull('foo');
`}
/>

### LIKE

To check if a column's value matches a pattern:

<QueryPreview
  code={`
Cond.like('foo', 'He_lo wor%');
`}
/>

### NOT LIKE

To check if a column's value does not match a pattern:

<QueryPreview
  code={`
Cond.notLike('foo', 'He_lo wor%');
`}
/>

### NOT

To negate any condition:

<QueryPreview
  code={`
Cond.not(Cond.equal('foo', 123));
`}
/>

You can negate complex conditions:

<QueryPreview
  code={`
Cond.not(
  Cond.and([
    Cond.equal('status', 'active'),
    Cond.greaterThan('age', 18)
  ])
);
`}
/>

Or negate other condition types:

<QueryPreview
  code={`
// NOT (foo IN (1, 2, 3))
Cond.not(Cond.in('foo', [1, 2, 3]));

// NOT (price BETWEEN 10 AND 50)
Cond.not(Cond.between('price', [10, 50]));

// NOT (name LIKE '%test%')
Cond.not(Cond.like('name', '%test%'));
`}
/>

## Conditions with functions

To check if a column's value does not match a pattern:

<QueryPreview
  code={`
Cond.notLike(Fn.sum('foo'), 'He_lo wor%');
`}
/>

## Conclusion

The Condition Builder offers a powerful and flexible way to construct SQL conditions in a readable manner. By abstracting the raw SQL syntax, it helps in reducing errors and improving maintainability.
