diff --git a/apps/framework-cli/src/framework/schema.rs b/apps/framework-cli/src/framework/schema.rs index aa535352d..bae414aa6 100644 --- a/apps/framework-cli/src/framework/schema.rs +++ b/apps/framework-cli/src/framework/schema.rs @@ -11,8 +11,9 @@ //! - Float //! - Decimal //! - DateTime +//! - Enum //! -//! We only implemented part of the prisma schema parsing. We only support models and fields. We don't support enums, relations, or anything else for the moment +//! We only implemented part of the prisma schema parsing. We only support models, enums and fields. We don't support relations, or anything else for the moment use std::collections::HashMap; use std::fmt::{Display, Formatter}; @@ -23,6 +24,8 @@ use std::{ use crate::framework::controller::FrameworkObject; use diagnostics::Diagnostics; + +use schema_ast::ast::{Enum, Model}; use schema_ast::{ ast::{Attribute, Field, SchemaAst, Top, WithName}, parse_schema, @@ -110,12 +113,26 @@ pub fn parse_schema_file( let ast = parse_schema(&schema_file, &mut diagnostics); - Ok(ast_mapper(ast)? + let file_objects = ast_mapper(ast)?; + + Ok(file_objects + .models .into_iter() .map(|data_model| mapper(data_model, path, version)) .collect()) } +pub struct FileObjects { + pub models: Vec, + pub enums: Vec, +} + +impl FileObjects { + pub fn new(models: Vec, enums: Vec) -> FileObjects { + FileObjects { models, enums } + } +} + #[derive(Debug, Clone, Serialize, Eq, PartialEq)] pub struct DataModel { pub db_name: String, @@ -123,6 +140,14 @@ pub struct DataModel { pub name: String, } +#[derive(Debug, Clone, Serialize, Eq, PartialEq)] +/// An internal framework representation for an enum. +/// Avoiding the use of the `Enum` keyword to avoid conflicts with Prisma's Enum type +pub struct DataEnum { + pub name: String, + pub values: Vec, +} + impl DataModel { pub fn to_table(&self, version: &str) -> Table { Table { @@ -213,6 +238,7 @@ pub enum ColumnType { Float, Decimal, DateTime, + Enum(DataEnum), Json, // TODO: Eventually support for only views and tables (not topics) Bytes, // TODO: Explore if we ever need this type Unsupported, @@ -273,13 +299,20 @@ impl FieldAttributes { } } -fn field_to_column(f: &Field) -> Result { +fn is_enum_type(string_type: &str, enums: &[DataEnum]) -> bool { + enums.iter().any(|e| e.name == string_type) +} + +fn field_to_column(f: &Field, enums: &[DataEnum]) -> Result { let attributes = FieldAttributes::new(f.attributes.clone())?; match &f.field_type { schema_ast::ast::FieldType::Supported(ft) => Ok(Column { name: f.name().to_string(), - data_type: map_column_string_type_to_column_type(ft.name.as_str()), + data_type: match is_enum_type(&ft.name, enums) { + true => ColumnType::Enum(enums.iter().find(|e| e.name == ft.name).unwrap().clone()), + false => map_column_string_type_to_column_type(&ft.name), + }, arity: arity_mapper(f.arity), unique: attributes.unique, primary_key: attributes.primary_key, @@ -293,28 +326,68 @@ fn field_to_column(f: &Field) -> Result { } } -fn top_to_schema(t: &Top) -> Result { - match t { - Top::Model(m) => { - let schema_name = m.name().to_string(); +fn prisma_model_to_datamodel(m: &Model, enums: &[DataEnum]) -> Result { + let schema_name = m.name().to_string(); - let columns: Result, ParsingError> = - m.iter_fields().map(|(_id, f)| field_to_column(f)).collect(); + let columns: Result, ParsingError> = m + .iter_fields() + .map(|(_id, f)| field_to_column(f, enums)) + .collect(); - Ok(DataModel { - db_name: "local".to_string(), - columns: columns?, - name: schema_name, - }) + Ok(DataModel { + db_name: "local".to_string(), + columns: columns?, + name: schema_name, + }) +} + +fn primsa_to_moose_enum(e: &Enum) -> DataEnum { + let name = e.name().to_string(); + let values = e + .iter_values() + .map(|(_id, v)| v.name().to_string()) + .collect(); + DataEnum { name, values } +} + +pub fn ast_mapper(ast: SchemaAst) -> Result { + let mut models = Vec::new(); + let mut enums = Vec::new(); + + ast.iter_tops().try_for_each(|(_id, t)| match t { + Top::Model(m) => { + models.push(m); + Ok(()) + } + Top::Enum(e) => { + enums.push(primsa_to_moose_enum(e)); + Ok(()) } _ => Err(ParsingError::UnsupportedDataTypeError { - type_name: "we don't currently support anything other than models".to_string(), + type_name: "we currently only support models and enums".to_string(), }), - } + })?; + + let parsed_models = models + .into_iter() + .map(|m| prisma_model_to_datamodel(m, &enums)) + .collect::, ParsingError>>()?; + + Ok(FileObjects::new(parsed_models, enums)) } -pub fn ast_mapper(ast: SchemaAst) -> Result, ParsingError> { - ast.iter_tops() - .map(|(_id, t)| top_to_schema(t)) - .collect::, ParsingError>>() +#[cfg(test)] +mod tests { + + use crate::framework::{controller::framework_object_mapper, schema::parse_schema_file}; + + #[test] + fn test_parse_schema_file() { + let current_dir = std::env::current_dir().unwrap(); + + let test_file = current_dir.join("tests/psl/simple.prisma"); + + let result = parse_schema_file(&test_file, "1.0", framework_object_mapper); + assert!(result.is_ok()); + } } diff --git a/apps/framework-cli/src/infrastructure/olap/clickhouse.rs b/apps/framework-cli/src/infrastructure/olap/clickhouse.rs index c267ac76c..e467bfb32 100644 --- a/apps/framework-cli/src/infrastructure/olap/clickhouse.rs +++ b/apps/framework-cli/src/infrastructure/olap/clickhouse.rs @@ -10,6 +10,7 @@ use log::debug; use regex::Regex; use serde::{Deserialize, Serialize}; +use crate::framework::schema::DataEnum; use crate::infrastructure::olap::clickhouse::queries::ClickhouseEngine; use crate::infrastructure::olap::clickhouse::queries::CreateVersionSyncTriggerQuery; @@ -49,6 +50,7 @@ pub enum ClickhouseColumnType { DateTime, Json, Bytes, + Enum(DataEnum), Unsupported, } diff --git a/apps/framework-cli/src/infrastructure/olap/clickhouse/mapper.rs b/apps/framework-cli/src/infrastructure/olap/clickhouse/mapper.rs index c52895aee..473e69fa3 100644 --- a/apps/framework-cli/src/infrastructure/olap/clickhouse/mapper.rs +++ b/apps/framework-cli/src/infrastructure/olap/clickhouse/mapper.rs @@ -21,6 +21,7 @@ pub fn std_field_type_to_clickhouse_type_mapper(field_type: ColumnType) -> Click ColumnType::Float => ClickhouseColumnType::ClickhouseFloat(ClickhouseFloat::Float64), ColumnType::Decimal => ClickhouseColumnType::Decimal, ColumnType::DateTime => ClickhouseColumnType::DateTime, + ColumnType::Enum(x) => ClickhouseColumnType::Enum(x), ColumnType::Unsupported => ClickhouseColumnType::Unsupported, _ => ClickhouseColumnType::Unsupported, } diff --git a/apps/framework-cli/src/infrastructure/olap/clickhouse/queries.rs b/apps/framework-cli/src/infrastructure/olap/clickhouse/queries.rs index 41dc1e915..9da2d24a9 100644 --- a/apps/framework-cli/src/infrastructure/olap/clickhouse/queries.rs +++ b/apps/framework-cli/src/infrastructure/olap/clickhouse/queries.rs @@ -321,6 +321,14 @@ fn field_type_to_string( }, ClickhouseColumnType::Decimal => Ok(field_type.to_string()), ClickhouseColumnType::DateTime => Ok(field_type.to_string()), + ClickhouseColumnType::Enum(x) => Ok(format!( + "Enum({})", + x.values + .iter() + .map(|x| format!("'{}'", x)) + .collect::>() + .join(", ") + )), _ => Err(UnsupportedDataTypeError { type_name: field_type.to_string(), }), @@ -352,3 +360,39 @@ fn clickhouse_column_to_create_table_field_context( }) } } + +#[cfg(test)] +mod tests { + + use crate::framework::{controller::framework_object_mapper, schema::parse_schema_file}; + + #[test] + fn test_create_query_from_prisma_model() { + let current_dir = std::env::current_dir().unwrap(); + + let test_file = current_dir.join("tests/psl/simple.prisma"); + + let result = parse_schema_file(&test_file, "1.0", framework_object_mapper).unwrap(); + + let ch_table = result[0].table.clone(); + + let query = ch_table.create_data_table_query().unwrap(); + + let expected = r#" +CREATE TABLE IF NOT EXISTS local.User_1_0 +( +id Int64 NOT NULL, +email String NOT NULL, +name String NULL, +role Enum('USER', 'ADMIN') NOT NULL, + + +PRIMARY KEY (id) + +) +ENGINE = MergeTree; +"#; + + assert_eq!(query, expected); + } +} diff --git a/apps/framework-cli/tests/psl/simple.prisma b/apps/framework-cli/tests/psl/simple.prisma index 00c5298c1..4ae427953 100644 --- a/apps/framework-cli/tests/psl/simple.prisma +++ b/apps/framework-cli/tests/psl/simple.prisma @@ -2,4 +2,10 @@ model User { id Int @id email String name String? + role Role +} + +enum Role { + USER + ADMIN } diff --git a/apps/moose-console/src/app/db.ts b/apps/moose-console/src/app/db.ts index 6a3733f5a..c5c2344a4 100644 --- a/apps/moose-console/src/app/db.ts +++ b/apps/moose-console/src/app/db.ts @@ -70,9 +70,16 @@ export interface DataModel { version: number; } +export interface MooseEnum { + Enum: { + name: string; + values: string[]; + }; +} + export interface Column { name: string; - data_type: string; + data_type: string | MooseEnum; arity: string; unique: boolean; primary_key: boolean; @@ -100,7 +107,7 @@ export interface Infra { ingestionPoints: Route[]; } -export function column_type_mapper(source_type) { +export function column_type_mapper(source_type: string) { switch (source_type) { case "String": return "string"; diff --git a/apps/moose-console/src/components/model-table.tsx b/apps/moose-console/src/components/model-table.tsx index 3fb8144ef..515bfb818 100644 --- a/apps/moose-console/src/components/model-table.tsx +++ b/apps/moose-console/src/components/model-table.tsx @@ -1,5 +1,15 @@ -import { DataModel } from "app/db"; +import { DataModel, MooseEnum } from "app/db"; import { Separator } from "./ui/separator"; +import { is_enum } from "../lib/utils"; + +const processType = (type: string | MooseEnum) => { + if (typeof type === "string") { + return type; + } else if (is_enum(type)) { + return type.Enum.name; + } + return JSON.stringify(type); +}; export default function ModelTable({ datamodel }: { datamodel: DataModel }) { return ( @@ -22,19 +32,15 @@ export default function ModelTable({ datamodel }: { datamodel: DataModel }) { {field.name}
- {" "} - {field.data_type} + {processType(field.data_type)}
- {" "} {field.arity}
- {" "} {`${field.unique}`}
- {" "} {`${field.primary_key}`}
diff --git a/apps/moose-console/src/lib/snippets.ts b/apps/moose-console/src/lib/snippets.ts index 039841950..3bd64ed18 100644 --- a/apps/moose-console/src/lib/snippets.ts +++ b/apps/moose-console/src/lib/snippets.ts @@ -1,8 +1,13 @@ import { CliData, DataModel, column_type_mapper } from "app/db"; -import { getIngestionPointFromModel } from "./utils"; +import { getIngestionPointFromModel, is_enum } from "./utils"; function createColumnStubs(model: DataModel) { return model.columns.map((field, index) => { + if (is_enum(field.data_type)) { + const value = JSON.stringify(field.data_type.Enum.values[0]); + return `"${field.name}": ${value}`; + } + const data_type = column_type_mapper(field.data_type); switch (data_type) { case "number": diff --git a/apps/moose-console/src/lib/utils.ts b/apps/moose-console/src/lib/utils.ts index d95f5c686..66a0b292b 100644 --- a/apps/moose-console/src/lib/utils.ts +++ b/apps/moose-console/src/lib/utils.ts @@ -61,3 +61,7 @@ export function getRelatedInfra( return { tables, ingestionPoints }; } + +export function is_enum(type: any): type is { Enum: any } { + return typeof type === "object" && type["Enum"] !== undefined; +} diff --git a/templates/mixplank/moose b/templates/mixplank/moose deleted file mode 160000 index 21f3b53df..000000000 --- a/templates/mixplank/moose +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 21f3b53dfd5bf558529fb4ab209110faf0cb3111 diff --git a/templates/mixplank/moose/.gitignore b/templates/mixplank/moose/.gitignore new file mode 100644 index 000000000..fc8cacac9 --- /dev/null +++ b/templates/mixplank/moose/.gitignore @@ -0,0 +1,5 @@ +.moose +node_modules +dist +coverage + diff --git a/templates/mixplank/moose/app/README.md b/templates/mixplank/moose/app/README.md new file mode 100644 index 000000000..653c91412 --- /dev/null +++ b/templates/mixplank/moose/app/README.md @@ -0,0 +1,40 @@ + +# Welcome to your new project! + +This is a new MooseJS project. You can find the documentation for MooseJS [here](https://docs.moosejs.com) + +## Getting Started + +To start your dev server, you can run the following commands: + +```bash +npm install +npm run dev +``` + +## Project Structure + +The project structured as follows: + +``` +project-root +│ README.md +│ package.json +│ .gitignore +│ .moose +└───app + └───datamodels + └───flows + └───insights + +``` + +## Data Models +You can find your data models in the `app/datamodels` directory. Data models define the structure of your data. You can find more information about data models [here](https://docs.moosejs.com) + +## Flows (coming soon) +We're currently working on implementing flows. You'll be able to add them to your project soon. + +## Insights (coming soon) +We're currently working on implementing insights. You'll be able to add them to your project soon. + diff --git a/templates/mixplank/moose/app/datamodels/models.prisma b/templates/mixplank/moose/app/datamodels/models.prisma new file mode 100644 index 000000000..a75928b46 --- /dev/null +++ b/templates/mixplank/moose/app/datamodels/models.prisma @@ -0,0 +1,10 @@ + +// This file was auto-generated by the framework. You can add data models or change the existing ones + +model UserActivity { + eventId String @id + timestamp DateTime + userId String + activity String +} + diff --git a/templates/mixplank/moose/package.json b/templates/mixplank/moose/package.json new file mode 100644 index 000000000..0cc69836d --- /dev/null +++ b/templates/mixplank/moose/package.json @@ -0,0 +1,11 @@ +{ + "name": "moose", + "version": "0.0", + "scripts": { + "dev": "moose-cli dev" + }, + "dependencies": {}, + "devDependencies": { + "@514labs/moose-cli": "latest" + } +} \ No newline at end of file diff --git a/templates/mixplank/next/package.json b/templates/mixplank/next/package.json index f7cc65cf1..10eed4e4a 100644 --- a/templates/mixplank/next/package.json +++ b/templates/mixplank/next/package.json @@ -26,3 +26,4 @@ "typescript": "^5" } } +