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

<Meta title="1. Query" />

# Query Builder

The Query Builder is a powerful tool that allows developers to construct SQL queries without having to write raw SQL. By offering a fluent interface, it simplifies the process of creating complex queries and ensures that the syntax is correct, reducing the risk of errors.

## Basic Usage

The foundation of any query starts with the `Q.select` method. This method initializes the query building process:

<QueryPreview code={`Q.select().from('tableName')`} />

### Selecting Fields

Instead of selecting all fields, you can specify which columns you want to retrieve:

<QueryPreview code={`Q.select().from('table').addField('fieldName')`} />

If you need to rename a column in the result set, you can provide an alias:

<QueryPreview
  code={`
Q.select().from('table').addField('fieldName', 'aliasName');
`}
/>

You can also add multiple fields at once:

<QueryPreview
  code={`
Q.select()
  .from('table')
  .addFields([
    { name: 'fieldName1', alias: 'aliasName1' },
    { name: 'fieldName2', alias: 'aliasName2' },
  ]);
`}
/>

Or you can reset all specified fields:

<QueryPreview
  code={`
Q.select()
  .from('table')
  .addField('fieldNameA')
  .fields([{ name: 'fieldNameB' }]);
`}
/>

### Table Aliasing:

Sometimes, for the sake of clarity or to avoid column name conflicts, you might want to alias a table:

<QueryPreview
  code={`
Q.select().from('table', 't');
`}
/>

### Union

<QueryPreview
  code={`
Q.select()
  .from('users')
  .union(Q.select().from('users2'))
`}
/>

### Joining Tables

Join operations are essential in SQL for combining rows from two or more tables based on a related column. The Query Builder supports various types of joins: INNER, LEFT, RIGHT, and FULL joins.

### Basic Join

To perform a basic join (INNER JOIN by default):

<QueryPreview
  code={`
Q.select()
  .from('users')
  .join(
    Q.table('orders', 'o'),
    Cond.columnEqual('users.id', 'o.user_id')
  );
`}
/>

### Specific Join Types

You can specify the type of join you need:

**Inner Join**: Combines rows from different tables when the join condition is met.

<QueryPreview
  code={`
Q.select()
  .from('users')
  .innerJoin(
    Q.table('orders', 'o'),
    Cond.columnEqual('users.id', 'o.user_id')
  );
`}
/>

**Left Join**: Returns all rows from the left table, and the matched rows from the right table. If no match, NULLs are returned for columns of the right table.

<QueryPreview
  code={`
Q.select()
  .from('users')
  .leftJoin(
    Q.table('orders', 'o'),
    Cond.columnEqual('users.id', 'o.user_id')
  );
`}
/>

**Right Join**: Similar to the left join, but returns all rows from the right table and matched rows from the left table.

<QueryPreview
  code={`
Q.select()
  .from('users')
  .rightJoin(
    Q.table('orders', 'o'),
    Cond.columnEqual('users.id', 'o.user_id')
  );
`}
/>

**Full Join**: Combines the results of both left and right joins. It returns rows when there is a match in either left or right table.

<QueryPreview
  code={`
Q.select()
  .from('users')
  .fullJoin(
    Q.table('orders', 'o'),
    Cond.columnEqual('users.id', 'o.user_id')
  );
`}
/>
### Cross Join

A cross join returns the Cartesian product of the two tables, combining each row from the first table with every row from the second table:

<QueryPreview
  code={`
Q.select()
  .from('users')
  .crossJoin(Q.table('orders', 'o'));
`}
/>

### Join Conditions

The join condition is specified using the `Conditions` object. This condition determines how the tables are joined.

For example, joining users and orders on user ID:

<QueryPreview
  code={`
Cond.columnEqual('users.id', 'o.user_id');
`}
/>

### Multiple Joins

You can chain multiple joins to combine more than two tables:

<QueryPreview
  code={`
Q.select()
  .from('users')
  .innerJoin(
    Q.table('orders', 'o'),
    Cond.columnEqual('users.id', 'o.user_id')
  )
  .leftJoin(
    Q.table('products', 'p'),
    Cond.columnEqual('o.product_id', 'p.id')
  );
`}
/>

### Aliasing in Joins

When joining tables, especially with multiple joins, it's often useful to alias tables for clarity and to resolve column name conflicts:

<QueryPreview
  code={`
Q.select()
  .from('users', 'u')
  .leftJoin(
    Q.table('orders', 'o'),
    Cond.columnEqual('u.id', 'o.user_id')
  );
`}
/>

### Complex Join Conditions

Join conditions can be more complex, involving logical operators like AND, OR:

<QueryPreview
  code={`
Q.select()
.from('users')
.innerJoin(Q.table('orders', 'o'), Cond.and([
  Cond.columnEqual('users.id', 'o.user_id'),
  Cond.greaterThan('orders.amount', 100),
]));
`}
/>

### Join with subquery

You can also join with a subquery as a table source. This is useful for aggregating or filtering data before joining:

<QueryPreview
  code={`
Q.select()
  .from('users', 'u')
  .leftJoin(
    Q.table(Q.select()
      .from('orders')
      .addField(Fn.sum('price','totalPrice'))
      .limit(10)
    , 'o'),
    Cond.columnEqual('u.id', 'o.user_id')
  );
`}
/>

### WHERE Conditions

Filtering the data you retrieve is a common operation. The `where` method allows you to specify conditions to limit the rows returned:

<QueryPreview
  code={`
Q.select().from('table').where(Cond.equal('fieldName', 'foo')).where(Cond.lessThan('fieldName2', 123));
`}
/>

### Optional WHERE Conditions

You can also add where conditions optionaly by providing empty values. For example all of these conditions are not applied

<QueryPreview
  code={`
Q.select()
  .from('table')
  .where(Cond.and([]))
  .where(Cond.or([]))
  .where(Cond.in('foo', 1>2 ? ['abc'] : []));
`}
/>

Also nested conditions are filtered

<QueryPreview
  code={`
Q.select()
  .from('table')
  .where(Cond.and([
    Cond.equal('foo','blah'), 
    Cond.or([
      Cond.in('foo',[])
    ])
  ]));
`}
/>

### LIMIT and OFFSET

In scenarios where you don't want to retrieve all rows, you can limit the result set:

<QueryPreview
  code={`
Q.select().from('table').limit(10);
`}
/>

If you want to skip a certain number of rows before retrieving the result set, use the offset method:

<QueryPreview
  code={`
Q.select().from('table').offset(5);
`}
/>

### ORDER BY

Sorting your result set can be achieved using the `orderBy` method:

<QueryPreview
  code={`
Q.select().from('table').orderBy('fieldName');
`}
/>

If you need the sorting to be in descending order:

<QueryPreview
  code={`
Q.select().from('table').orderBy('fieldName').orderBy('fieldName2', 'DESC');
`}
/>

You can also reset order by configuration:

<QueryPreview
  code={`
Q.select().from('table').orderBy('fieldName').removeOrderBy().orderBy('fieldName2', 'DESC');
`}
/>

### GROUP BY

When you want to group rows that have the same values in specified columns into summary rows:

<QueryPreview
  code={`
Q.select().from('table').groupBy('fieldName').groupBy('fieldName2');
`}
/>

You can also reset group by configuration:

<QueryPreview
  code={`
Q.select().from('table').groupBy('fieldName').removeGroupBy().groupBy('fieldName2','DESC');
`}
/>

### Complex Table Sources:

For more advanced queries, you might need to use subqueries as a table source:

<QueryPreview
  code={`
Q.select().from(Q.select().from('table').where(Cond.equal('field', 'blah')),'subt');
`}
/>

## Mutations

You can also generate insert/update/delete statements

### Insert

<QueryPreview
  code={`
Q.insert('table').values([{foo:'blah'}]);
`}
/>

### Insert select

<QueryPreview
  code={`
Q.insert('table').select(Q.select().from('table2'));
`}
/>

You can also specify column names:

<QueryPreview
  code={`
Q.insert('user_stats').select(Q.select().from('users').addField('region').addField(Fn.count('*')).groupBy('region'),['region','count']);
`}
/>

### Update

<QueryPreview
  code={`
Q.update('table')
  .set({ 
    foo: 'blah', 
    total: Fn.divide(Fn.multiply('amount','price'),Q.raw(2)) 
  }).where(Cond.equal('name','john'));
`}
/>

### Delete

<QueryPreview
  code={`
Q.delete('table').where(Cond.equal('id',123));
`}
/>

## Examples

### Basic Select Query

A simple example to retrieve all columns from a table:

<QueryPreview
  code={`
Q.select().from('foo');
`}
/>

### Query with Multiple Conditions

A more complex example showcasing multiple conditions:

<QueryPreview
  code={`
Q.select()
  .from('users')
  .addField('id')
  .addField('name')
  .where(
    Cond.and([
      Cond.equal('age', 25),
      Cond.or([
        Cond.greaterThan('salary', 50000),
        Cond.like('position', 'manager%'),
      ]),
    ])
  )
  .orderBy('salary', 'DESC')
  .limit(10)
  .offset(5);
`}
/>

### Query with Nested Conditions

Queries can also have nested conditions for more intricate filtering:

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

## Immutability

One of the key features of the Query Builder is its immutability. This ensures that once a query is constructed, it cannot be changed, preventing unintended side-effects:

```tsx
const originalQuery = Q.select().from("table").orderBy("bar", "DESC");
const newQuery = originalQuery.orderBy("foo");
```

This design ensures that queries can be safely reused and extended without affecting the original query.

## Advanced Conditions:

Advanced conditions provide more flexibility in filtering data:

- `Cond.in(...)`: Checks if a column's value is within a set of values.
- `Cond.null(...)`: Checks if a column's value is NULL.
- `Cond.notNull(...)`: Checks if a column's value is NOT NULL.
- `Cond.columnEqual(...)`: Compares two columns for equality.

## Cloning:

If you need to create a copy of a query to make variations without affecting the original, you can use the `clone` method:

```tsx
originalQuery.clone();
```

## Serialization:

For scenarios where you might need to store the query structure or send it across a network, the Query Builder provides serialization methods:

```tsx
const serialized = query.serialize();
SelectQuery.deserialize(serialized);
```
