Skip to content

Commit

Permalink
feat(typefusion): clickhouse support
Browse files Browse the repository at this point in the history
closed #29
  • Loading branch information
aniravi24 committed Oct 23, 2024
1 parent db7b84d commit f7fa6c7
Show file tree
Hide file tree
Showing 17 changed files with 1,112 additions and 966 deletions.
5 changes: 5 additions & 0 deletions .changeset/lovely-actors-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"typefusion": patch
---

Clickhouse support
3 changes: 2 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
# start preparing the database containers, if you run tests before these start up, you may get an error
docker run -d -p 5432:5432 -e POSTGRES_DB=typefusion -e POSTGRES_PASSWORD=password postgres:alpine
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=typefusion mysql:latest
docker run -d -p 8123:8123 clickhouse/clickhouse-server:latest
- name: Checkout repo
uses: actions/checkout@v4
Expand All @@ -41,7 +42,7 @@ jobs:
env:
PG_DATABASE_URL: postgres://postgres:password@localhost:5432/typefusion?sslmode=disable
MYSQL_DATABASE_URL: mysql://root:password@localhost:3306/typefusion

CLICKHOUSE_DATABASE_URL: http://localhost:8123/
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
env:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ To begin using Typefusion, follow these steps:
bun add typefusion
```

2. Configure your database connection using one of these methods (PostgreSQL and MySQL are supported):
2. Configure your database connection using one of these methods (PostgreSQL, MySQL, and Clickhouse are supported):

- Set a full connection string in the `PG_DATABASE_URL` or `MYSQL_DATABASE_URL` environment variable.
- Set individual environment variables: `PG_DATABASE`, `PG_HOST`, `PG_PORT`, `PG_PASSWORD`, and `PG_USER` (for postgres) or `MYSQL_DATABASE`, `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_PASSWORD`, and `MYSQL_USER` (for mysql).
- Set a full connection string in the `PG_DATABASE_URL` or `MYSQL_DATABASE_URL` or `CLICKHOUSE_DATABASE_URL` (and optionally `CLICKHOUSE_USER`, `CLICKHOUSE_PASSWORD`, and `CLICKHOUSE_DATABASE` if not using the defaults) environment variable.
- Set individual environment variables: `PG_DATABASE`, `PG_HOST`, `PG_PORT`, `PG_PASSWORD`, and `PG_USER` (for postgres) or `MYSQL_DATABASE`, `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_PASSWORD`, and `MYSQL_USER` (for mysql) or `CLICKHOUSE_DATABASE`, `CLICKHOUSE_HOST`, `CLICKHOUSE_PORT`, `CLICKHOUSE_PASSWORD`, and `CLICKHOUSE_USER` (for clickhouse).

3. Create a directory for your scripts (e.g., `workflows`).

Expand All @@ -64,7 +64,7 @@ To begin using Typefusion, follow these steps:
After following the above instructions, create a script file in the directory, for example, `main.ts`:

```ts
// or mySqlType for mysql
// or mySqlType for mysql or clickhouseType for clickhouse
import { pgType, typefusionRef, TypefusionDbScript } from "typefusion";

const mainSchema = {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
"@commitlint/cli": "19.5.0",
"@commitlint/config-conventional": "19.5.0",
"@manypkg/cli": "0.22.0",
"@typescript-eslint/eslint-plugin": "8.8.1",
"@typescript-eslint/parser": "8.8.1",
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "8.11.0",
"commitizen": "4.3.1",
"cz-git": "1.10.1",
"eslint": "9.13.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { clickhouseType, TypefusionDbScript } from "../../src/index.js";

const allClickhouseTypes = {
text: clickhouseType.string().notNull(),
int: clickhouseType.int32().notNull(),
boolean: clickhouseType.boolean().notNull(),
date: clickhouseType.date().notNull(),
dateTime: clickhouseType.dateTime64(3).notNull(),
bigint: clickhouseType.int64().notNull(),
smallint: clickhouseType.int16().notNull(),
float: clickhouseType.float32().notNull(),
double: clickhouseType.float64().notNull(),
decimal: clickhouseType.decimal(10, 2).notNull(),
char: clickhouseType.fixedString(10).notNull(),
varchar: clickhouseType.string().notNull(),
time: clickhouseType.string().notNull(), // Clickhouse doesn't have a specific time type
json: clickhouseType.json().notNull(),
binary: clickhouseType.string().notNull(), // Using string as a close approximation
};

export default {
name: "typefusion_all_clickhouse_types",
schema: allClickhouseTypes,
resultDatabase: "clickhouse",
run: async () => {
console.log("TYPEFUSION ALL CLICKHOUSE TYPES IS RUN");
return {
data: [
{
text: "Sample text",
int: 42,
boolean: true,
date: "2023-05-17",
dateTime: "2023-05-17T12:34:56",
bigint: BigInt("9007199254740991").toString(),
smallint: 32767,
float: 3.14,
double: 3.141592653589793,
decimal: 123.45,
char: "Fixed ",
varchar: "Variable length text",
time: "12:34:56",
// TODO this needs to be a json string
json: { key: "value" },
binary: Buffer.from([0x12]).toString("base64"), // Convert to base64 string
},
],
};
},
} satisfies TypefusionDbScript<typeof allClickhouseTypes>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
typefusionRef,
TypefusionDbScript,
clickhouseType,
} from "../../src/index.js";
import main from "../main.js";

const smallSchema = {
small: clickhouseType.string().notNull(),
};

export default {
name: "typefusion_clickhouse_result",
resultDatabase: "clickhouse",
schema: smallSchema,
run: async () => {
const result = await typefusionRef(main);
console.log("TYPEFUSION CLICKHOUSE RESULT IS RUN", result);
return {
data: [
{
small: "smallString" as const,
},
],
};
},
} satisfies TypefusionDbScript<typeof smallSchema>;
22 changes: 11 additions & 11 deletions packages/typefusion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,25 @@
"example-debug": "dotenv -- tsx src/cli.ts --log-level debug --ignore \"**/src/**\" ./test/examplejs"
},
"dependencies": {
"@effect/cli": "0.46.1",
"@effect/platform": "0.67.1",
"@effect/platform-node": "0.62.1",
"@effect/schema": "0.75.5",
"@effect/sql": "0.15.1",
"@effect/sql-mysql2": "0.15.2",
"@effect/sql-pg": "0.15.2",
"effect": "3.9.2",
"@effect/cli": "0.48.5",
"@effect/platform": "0.69.5",
"@effect/platform-node": "0.64.6",
"@effect/sql": "0.18.6",
"@effect/sql-clickhouse": "0.2.6",
"@effect/sql-mysql2": "0.18.6",
"@effect/sql-pg": "0.18.6",
"effect": "3.10.1",
"postgres": "3.4.4",
"skott": "0.35.3",
"tslib": "2.7.0"
"tslib": "2.8.0"
},
"devDependencies": {
"@effect/vitest": "0.12.1",
"@effect/vitest": "0.13.1",
"@vitest/coverage-v8": "2.1.3",
"dotenv-cli": "7.4.2",
"tsx": "4.19.1",
"type-fest": "4.26.1",
"vite": "5.4.9",
"vite": "5.4.10",
"vite-tsconfig-paths": "5.0.1",
"vitest": "2.1.3"
}
Expand Down
92 changes: 92 additions & 0 deletions packages/typefusion/src/db/clickhouse/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { ClickhouseClient } from "@effect/sql-clickhouse";
import { Config, Effect, Layer } from "effect";
import { DatabaseHelper } from "../common/service.js";
import {
chDropTableIfExists,
chCreateTableIfNotExists,
chInsertIntoTable,
chSelectAllFromTable,
chColumnDDL,
} from "./helpers.js";
import { valueToClickhouseType, clickhouseIdColumn } from "./types.js";

/**
* @internal
*/
export const ClickhouseDatabaseConfig = Config.all({
databaseUrl: Config.orElse(Config.string("CLICKHOUSE_DATABASE_URL"), () =>
Config.map(
Config.all({
CLICKHOUSE_HOST: Config.string("CLICKHOUSE_HOST"),
CLICKHOUSE_PORT: Config.integer("CLICKHOUSE_PORT"),
}),
({ CLICKHOUSE_HOST, CLICKHOUSE_PORT }) =>
`http://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT}/`,
),
),
username: Config.string("CLICKHOUSE_USER").pipe(
Config.orElse(() => Config.succeed(undefined)),
),
password: Config.string("CLICKHOUSE_PASSWORD").pipe(
Config.orElse(() => Config.succeed(undefined)),
),
database: Config.string("CLICKHOUSE_DATABASE").pipe(
Config.orElse(() => Config.succeed(undefined)),
),
});

/**
* @internal
*/
const ClickhouseLive = ClickhouseClient.layer({
url: Config.map(ClickhouseDatabaseConfig, (c) => c.databaseUrl),
username: Config.map(ClickhouseDatabaseConfig, (c) => c.username),
password: Config.map(ClickhouseDatabaseConfig, (c) => c.password),
database: Config.map(ClickhouseDatabaseConfig, (c) => c.database),
clickhouse_settings: {
allow_experimental_json_type: Config.succeed(true),
},
});

/**
* @internal
*/
export class ClickhouseService extends Effect.Service<ClickhouseService>()(
"@typefusion/clickhouse",
{
effect: ClickhouseClient.ClickhouseClient,
dependencies: [ClickhouseLive],
},
) {}

/**
* @internal
*/
export const ClickhouseDatabaseHelperLive = Layer.succeed(DatabaseHelper, {
valueToDbType: valueToClickhouseType,
idColumn: clickhouseIdColumn,
dropTableIfExists: chDropTableIfExists,
createTableIfNotExists: chCreateTableIfNotExists,
insertIntoTable: chInsertIntoTable,
selectAllFromTable: chSelectAllFromTable,
columnDDL: chColumnDDL,
});

/**
* @internal
*/
export class ClickhouseDatabaseHelperService extends Effect.Service<ClickhouseDatabaseHelperService>()(
"@typefusion/clickhouse/databasehelper",
{
effect: DatabaseHelper,
dependencies: [ClickhouseDatabaseHelperLive],
},
) {}

/**
* @internal
*/
export const ClickhouseFinalLive = Layer.mergeAll(
ClickhouseService.Default,
ClickhouseDatabaseHelperService.Default,
);
48 changes: 48 additions & 0 deletions packages/typefusion/src/db/clickhouse/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ClickhouseClient } from "@effect/sql-clickhouse/ClickhouseClient";
import { SqlClient } from "@effect/sql/SqlClient";
import { Effect } from "effect";
/**
* @internal
*/
export const chDropTableIfExists = (
sql: SqlClient | ClickhouseClient,
tableName: string,
) => sql`DROP TABLE IF EXISTS ${sql.unsafe(tableName)}`;

/**
* @internal
*/
export const chCreateTableIfNotExists = (
sql: SqlClient | ClickhouseClient,
tableName: string,
columnDefinitions: string,
) => {
const firstColumnName = columnDefinitions.split(" ")[0];
return sql`CREATE TABLE IF NOT EXISTS ${sql.unsafe(tableName)} (${sql.unsafe(columnDefinitions)}) ORDER BY (${sql.unsafe(firstColumnName)})`;
};

/**
* @internal
*/
export const chInsertIntoTable = (
sql: SqlClient | ClickhouseClient,
tableName: string,
data: unknown[],
) =>
(sql as ClickhouseClient)
.insertQuery({ table: tableName, values: data })
.pipe(Effect.asVoid);

/**
* @internal
*/
export const chSelectAllFromTable = (
sql: SqlClient | ClickhouseClient,
tableName: string,
) => sql`SELECT * FROM ${sql.unsafe(tableName)}`;

/**
* @internal
*/
export const chColumnDDL = (columnName: string, columnType: string) =>
`${columnName} ${columnType}`;
Loading

0 comments on commit f7fa6c7

Please sign in to comment.