Skip to content

Commit 1f2b7ca

Browse files
adminadmin
authored andcommitted
Add null engine table implementation
1 parent 491bc61 commit 1f2b7ca

File tree

26 files changed

+417
-42
lines changed

26 files changed

+417
-42
lines changed

apps/framework-cli/src/framework/core/partial_infrastructure_map.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ struct DistributedConfig {
159159
#[derive(Debug, Deserialize)]
160160
#[serde(tag = "engine", rename_all = "camelCase")]
161161
enum EngineConfig {
162+
#[serde(rename = "Null")]
163+
Null {},
164+
162165
#[serde(rename = "MergeTree")]
163166
MergeTree {},
164167

@@ -747,6 +750,8 @@ impl PartialInfrastructureMap {
747750
partial_table: &PartialTable,
748751
) -> Result<ClickhouseEngine, DmV2LoadingError> {
749752
match &partial_table.engine_config {
753+
Some(EngineConfig::Null {}) => Ok(ClickhouseEngine::Null),
754+
750755
Some(EngineConfig::MergeTree {}) => Ok(ClickhouseEngine::MergeTree),
751756

752757
Some(EngineConfig::ReplacingMergeTree { ver, is_deleted }) => {

apps/framework-cli/src/framework/python/generate.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,12 @@ pub fn tables_to_python(tables: &[Table], life_cycle: Option<LifeCycle>) -> Stri
780780
crate::infrastructure::olap::clickhouse::queries::ClickhouseEngine::MergeTree => {
781781
writeln!(output, " engine=MergeTreeEngine(),").unwrap();
782782
}
783+
crate::infrastructure::olap::clickhouse::queries::ClickhouseEngine::Null => {
784+
// No explicit engine for Null in moose_lib,
785+
// so we do not generate an `engine=...` parameter in OlapConfig.
786+
// (If you want a real Null ENGINE later, we can adapt here.)
787+
}
788+
783789
crate::infrastructure::olap::clickhouse::queries::ClickhouseEngine::ReplacingMergeTree { ver, is_deleted } => {
784790
// Emit ReplacingMergeTreeEngine with parameters if present
785791
write!(output, " engine=ReplacingMergeTreeEngine(").unwrap();

apps/framework-cli/src/framework/typescript/generate.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,11 @@ pub fn tables_to_typescript(tables: &[Table], life_cycle: Option<LifeCycle>) ->
685685
writeln!(output, " }},").unwrap();
686686
}
687687
}
688+
crate::infrastructure::olap::clickhouse::queries::ClickhouseEngine::Null => {
689+
// Table with engine Null : we expose the Null engine in TS
690+
// (assuming you have ClickHouseEngines.Null in your TS enum)
691+
writeln!(output, " engine: ClickHouseEngines.Null,").unwrap();
692+
}
688693
crate::infrastructure::olap::clickhouse::queries::ClickhouseEngine::MergeTree => {
689694
writeln!(output, " engine: ClickHouseEngines.MergeTree,").unwrap();
690695
}

apps/framework-cli/src/infrastructure/olap/clickhouse/queries.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ impl BufferEngine {
234234
pub enum ClickhouseEngine {
235235
#[default]
236236
MergeTree,
237+
Null,
237238
ReplacingMergeTree {
238239
// Optional version column for deduplication
239240
ver: Option<String>,
@@ -330,6 +331,7 @@ pub enum ClickhouseEngine {
330331
impl Into<String> for ClickhouseEngine {
331332
fn into(self) -> String {
332333
match self {
334+
ClickhouseEngine::Null => "Null".to_string(),
333335
ClickhouseEngine::MergeTree => "MergeTree".to_string(),
334336
ClickhouseEngine::ReplacingMergeTree { ver, is_deleted } => {
335337
Self::serialize_replacing_merge_tree(&ver, &is_deleted)
@@ -422,6 +424,11 @@ impl<'a> TryFrom<&'a str> for ClickhouseEngine {
422424
type Error = &'a str;
423425

424426
fn try_from(value: &'a str) -> Result<Self, &'a str> {
427+
// Try to parse Null engine
428+
if value.eq_ignore_ascii_case("Null") {
429+
return Ok(ClickhouseEngine::Null);
430+
}
431+
425432
// Try to parse distributed variants first (SharedMergeTree, ReplicatedMergeTree)
426433
if let Some(engine) = Self::try_parse_distributed_engine(value) {
427434
return engine;
@@ -775,6 +782,7 @@ impl ClickhouseEngine {
775782
let engine_name = value.strip_prefix("Shared").unwrap_or(value);
776783

777784
match engine_name {
785+
"Null" => Ok(ClickhouseEngine::Null),
778786
"MergeTree" => Ok(ClickhouseEngine::MergeTree),
779787
"ReplacingMergeTree" => Ok(ClickhouseEngine::ReplacingMergeTree {
780788
ver: None,
@@ -1050,6 +1058,7 @@ impl ClickhouseEngine {
10501058
/// Convert engine to string for proto storage (no sensitive data)
10511059
pub fn to_proto_string(&self) -> String {
10521060
match self {
1061+
ClickhouseEngine::Null => "Null".to_string(),
10531062
ClickhouseEngine::MergeTree => "MergeTree".to_string(),
10541063
ClickhouseEngine::ReplacingMergeTree { ver, is_deleted } => {
10551064
Self::serialize_replacing_merge_tree(ver, is_deleted)
@@ -1745,6 +1754,9 @@ impl ClickhouseEngine {
17451754
// Without hashing "null", both would produce identical hashes.
17461755

17471756
match self {
1757+
ClickhouseEngine::Null => {
1758+
hasher.update("Null".as_bytes());
1759+
}
17481760
ClickhouseEngine::MergeTree => {
17491761
hasher.update("MergeTree".as_bytes());
17501762
}
@@ -2249,6 +2261,7 @@ pub fn create_table_query(
22492261
reg.register_escape_fn(no_escape);
22502262

22512263
let engine = match &table.engine {
2264+
ClickhouseEngine::Null => "Null".to_string(),
22522265
ClickhouseEngine::MergeTree => "MergeTree".to_string(),
22532266
ClickhouseEngine::ReplacingMergeTree { ver, is_deleted } => build_replacing_merge_tree_ddl(
22542267
ver,
@@ -4927,6 +4940,16 @@ ENGINE = S3Queue('s3://my-bucket/data/*.csv', NOSIGN, 'CSV')"#;
49274940
}
49284941
}
49294942

4943+
#[test]
4944+
fn test_null_engine_roundtrip() {
4945+
let engine_str = "Null";
4946+
let engine: ClickhouseEngine = engine_str.try_into().unwrap();
4947+
assert!(matches!(engine, ClickhouseEngine::Null));
4948+
4949+
let serialized: String = engine.into();
4950+
assert_eq!(serialized, "Null");
4951+
}
4952+
49304953
#[test]
49314954
fn test_buffer_engine_round_trip() {
49324955
// Test Buffer engine with all parameters

apps/framework-cli/src/infrastructure/olap/clickhouse/sql_parser.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,4 +1557,20 @@ pub mod tests {
15571557
let indexes = extract_indexes_from_create_table(NESTED_OBJECTS_SQL).unwrap();
15581558
assert_eq!(indexes.len(), 0);
15591559
}
1560+
1561+
#[test]
1562+
fn test_extract_null_engine() {
1563+
// Test for engine = Null
1564+
let sql = "CREATE TABLE test (x Int32) ENGINE = Null";
1565+
let result = extract_engine_from_create_table(sql);
1566+
assert_eq!(result, Some("Null".to_string()));
1567+
}
1568+
1569+
#[test]
1570+
fn test_extract_null_engine_lowercase() {
1571+
// Test for engine = null (lowercase)
1572+
let sql = "create table test (x Int32) engine = null";
1573+
let result = extract_engine_from_create_table(sql);
1574+
assert_eq!(result, Some("null".to_string()));
1575+
}
15601576
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.moose
2+
__pycache__
3+
*.pyc
4+
*.pyo
5+
*.pyd
6+
.Python
7+
env
8+
.venv
9+
venv
10+
ENV
11+
env.bak
12+
.spyderproject
13+
.ropeproject
14+
.idea
15+
*.ipynb_checkpoints
16+
.pytest_cache
17+
.mypy_cache
18+
.hypothesis
19+
.coverage
20+
cover
21+
*.cover
22+
.DS_Store
23+
.cache
24+
*.so
25+
*.egg
26+
*.egg-info
27+
dist
28+
build
29+
develop-eggs
30+
downloads
31+
eggs
32+
lib
33+
lib64
34+
parts
35+
sdist
36+
var
37+
wheels
38+
*.egg-info/
39+
.installed.cfg
40+
*.egg
41+
MANIFEST
42+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"recommendations": [
3+
"frigus02.vscode-sql-tagged-template-literals-syntax-only",
4+
"mtxr.sqltools",
5+
"ultram4rine.sqltools-clickhouse-driver",
6+
"jeppeandersen.vscode-kafka",
7+
"rangav.vscode-thunder-client"
8+
]
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"sqltools.connections": [
3+
{
4+
"server": "localhost",
5+
"port": 18123,
6+
"useHTTPS": false,
7+
"database": "local",
8+
"username": "panda",
9+
"enableTls": false,
10+
"password": "pandapass",
11+
"driver": "ClickHouse",
12+
"name": "moose clickhouse"
13+
}
14+
],
15+
"python.analysis.extraPaths": [".moose/versions"],
16+
"python.analysis.typeCheckingMode": "basic"
17+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Template: Python
2+
3+
This is a Python-based Moose template that provides a foundation for building data-intensive applications using Python.
4+
5+
[![PyPI Version](https://img.shields.io/pypi/v/moose-cli?logo=python)](https://pypi.org/project/moose-cli/)
6+
[![Moose Community](https://img.shields.io/badge/slack-moose_community-purple.svg?logo=slack)](https://join.slack.com/t/moose-community/shared_invite/zt-2fjh5n3wz-cnOmM9Xe9DYAgQrNu8xKxg)
7+
[![Docs](https://img.shields.io/badge/quick_start-docs-blue.svg)](https://docs.fiveonefour.com/moose/getting-started/quickstart)
8+
[![MIT license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)
9+
10+
## Getting Started
11+
12+
### Prerequisites
13+
14+
* [Docker Desktop](https://www.docker.com/products/docker-desktop/)
15+
* [Python](https://www.python.org/downloads/) (version 3.8+)
16+
* [An Anthropic API Key](https://docs.anthropic.com/en/api/getting-started)
17+
* [Cursor](https://www.cursor.com/) or [Claude Desktop](https://claude.ai/download)
18+
19+
### Installation
20+
21+
1. Install Moose CLI: `pip install moose-cli`
22+
2. Create project: `moose init <project-name> python`
23+
3. Install dependencies: `cd <project-name> && pip install -r requirements.txt`
24+
4. Run Moose: `moose dev`
25+
26+
You are ready to go! You can start editing the app by modifying primitives in the `app` subdirectory.
27+
28+
## Learn More
29+
30+
To learn more about Moose, take a look at the following resources:
31+
32+
- [Moose Documentation](https://docs.fiveonefour.com/moose) - learn about Moose.
33+
- [Sloan Documentation](https://docs.fiveonefour.com/sloan) - learn about Sloan, the MCP interface for data engineering.
34+
35+
## Community
36+
37+
You can join the Moose community [on Slack](https://join.slack.com/t/moose-community/shared_invite/zt-2fjh5n3wz-cnOmM9Xe9DYAgQrNu8xKxg). Check out the [MooseStack repo on GitHub](https://github.com/514-labs/moosestack).
38+
39+
## Deploy on Boreal
40+
41+
The easiest way to deploy your MooseStack Applications is to use [Boreal](https://www.fiveonefour.com/boreal) from 514 Labs, the creators of Moose.
42+
43+
[Sign up](https://www.boreal.cloud/sign-up).
44+
45+
## License
46+
47+
This template is MIT licensed.
48+

examples/null-engine-example/null-engine-example-py/app/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)