Skip to content

Commit

Permalink
adding enum parsing and added to test (#478)
Browse files Browse the repository at this point in the history
* Added enum functionality to prisma
  • Loading branch information
tg339 authored Feb 29, 2024
1 parent ba35cb7 commit 9ed233e
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 31 deletions.
115 changes: 94 additions & 21 deletions apps/framework-cli/src/framework/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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,
Expand Down Expand Up @@ -110,19 +113,41 @@ pub fn parse_schema_file<O>(

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<DataModel>,
pub enums: Vec<DataEnum>,
}

impl FileObjects {
pub fn new(models: Vec<DataModel>, enums: Vec<DataEnum>) -> FileObjects {
FileObjects { models, enums }
}
}

#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
pub struct DataModel {
pub db_name: String,
pub columns: Vec<Column>,
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<String>,
}

impl DataModel {
pub fn to_table(&self, version: &str) -> Table {
Table {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -273,13 +299,20 @@ impl FieldAttributes {
}
}

fn field_to_column(f: &Field) -> Result<Column, ParsingError> {
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<Column, ParsingError> {
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,
Expand All @@ -293,28 +326,68 @@ fn field_to_column(f: &Field) -> Result<Column, ParsingError> {
}
}

fn top_to_schema(t: &Top) -> Result<DataModel, ParsingError> {
match t {
Top::Model(m) => {
let schema_name = m.name().to_string();
fn prisma_model_to_datamodel(m: &Model, enums: &[DataEnum]) -> Result<DataModel, ParsingError> {
let schema_name = m.name().to_string();

let columns: Result<Vec<Column>, ParsingError> =
m.iter_fields().map(|(_id, f)| field_to_column(f)).collect();
let columns: Result<Vec<Column>, 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<FileObjects, ParsingError> {
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::<Result<Vec<DataModel>, ParsingError>>()?;

Ok(FileObjects::new(parsed_models, enums))
}

pub fn ast_mapper(ast: SchemaAst) -> Result<Vec<DataModel>, ParsingError> {
ast.iter_tops()
.map(|(_id, t)| top_to_schema(t))
.collect::<Result<Vec<DataModel>, 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());
}
}
2 changes: 2 additions & 0 deletions apps/framework-cli/src/infrastructure/olap/clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -49,6 +50,7 @@ pub enum ClickhouseColumnType {
DateTime,
Json,
Bytes,
Enum(DataEnum),
Unsupported,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
44 changes: 44 additions & 0 deletions apps/framework-cli/src/infrastructure/olap/clickhouse/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>()
.join(", ")
)),
_ => Err(UnsupportedDataTypeError {
type_name: field_type.to_string(),
}),
Expand Down Expand Up @@ -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);
}
}
6 changes: 6 additions & 0 deletions apps/framework-cli/tests/psl/simple.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ model User {
id Int @id
email String
name String?
role Role
}

enum Role {
USER
ADMIN
}
11 changes: 9 additions & 2 deletions apps/moose-console/src/app/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down
18 changes: 12 additions & 6 deletions apps/moose-console/src/components/model-table.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -22,19 +32,15 @@ export default function ModelTable({ datamodel }: { datamodel: DataModel }) {
{field.name}
</div>
<div className="grow basis-1 text-muted-foreground">
{" "}
{field.data_type}
{processType(field.data_type)}
</div>
<div className="grow basis-1 text-muted-foreground">
{" "}
{field.arity}
</div>
<div className="grow basis-1 text-muted-foreground">
{" "}
{`${field.unique}`}
</div>
<div className="grow basis-1 text-muted-foreground">
{" "}
{`${field.primary_key}`}
</div>
</div>
Expand Down
7 changes: 6 additions & 1 deletion apps/moose-console/src/lib/snippets.ts
Original file line number Diff line number Diff line change
@@ -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":
Expand Down
4 changes: 4 additions & 0 deletions apps/moose-console/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
1 change: 0 additions & 1 deletion templates/mixplank/moose
Submodule moose deleted from 21f3b5
5 changes: 5 additions & 0 deletions templates/mixplank/moose/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.moose
node_modules
dist
coverage

Loading

0 comments on commit 9ed233e

Please sign in to comment.