Skip to content

Commit 12571db

Browse files
authored
Merge pull request #1 from github/dbscheme
Basic dbscheme generation from `node-types.json`
2 parents 735fde7 + 36823d7 commit 12571db

File tree

5 files changed

+2454
-1
lines changed

5 files changed

+2454
-1
lines changed

generator/src/dbscheme.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
use std::fmt;
2+
3+
/// Represents a distinct entry in the database schema.
4+
pub enum Entry {
5+
/// An entry defining a database table.
6+
Table(Table),
7+
8+
/// An entry defining type that is a union of other types.
9+
Union(Union),
10+
}
11+
12+
/// A table in the database schema.
13+
pub struct Table {
14+
pub name: String,
15+
pub columns: Vec<Column>,
16+
pub keysets: Option<Vec<String>>,
17+
}
18+
19+
/// A union in the database schema.
20+
pub struct Union {
21+
pub name: String,
22+
pub members: Vec<String>,
23+
}
24+
25+
/// A column in a table.
26+
pub struct Column {
27+
pub db_type: DbColumnType,
28+
pub name: String,
29+
pub unique: bool,
30+
pub ql_type: QlColumnType,
31+
pub ql_type_is_ref: bool,
32+
}
33+
34+
/// The database column type.
35+
pub enum DbColumnType {
36+
Int,
37+
String,
38+
}
39+
40+
// The QL type of a column.
41+
pub enum QlColumnType {
42+
/// Primitive `int` type.
43+
Int,
44+
45+
/// Primitive `string` type.
46+
String,
47+
48+
/// A custom type, defined elsewhere by a table or union.
49+
Custom(String),
50+
}
51+
52+
const RESERVED_KEYWORDS: [&'static str; 14] = [
53+
"boolean", "case", "date", "float", "int", "key", "of", "order", "ref", "string", "subtype",
54+
"type", "unique", "varchar",
55+
];
56+
57+
/// Returns a string that's a copy of `name` but suitably escaped to be a valid
58+
/// QL identifier.
59+
pub fn escape_name(name: &str) -> String {
60+
let mut result = String::new();
61+
62+
// If there's a leading underscore, replace it with 'underscore_'.
63+
if let Some(c) = name.chars().next() {
64+
if c == '_' {
65+
result.push_str("underscore");
66+
}
67+
}
68+
for c in name.chars() {
69+
match c {
70+
'{' => result.push_str("lbrace"),
71+
'}' => result.push_str("rbrace"),
72+
'<' => result.push_str("langle"),
73+
'>' => result.push_str("rangle"),
74+
'[' => result.push_str("lbracket"),
75+
']' => result.push_str("rbracket"),
76+
'(' => result.push_str("lparen"),
77+
')' => result.push_str("rparen"),
78+
'|' => result.push_str("pipe"),
79+
'=' => result.push_str("equal"),
80+
'~' => result.push_str("tilde"),
81+
'?' => result.push_str("question"),
82+
'`' => result.push_str("backtick"),
83+
'^' => result.push_str("caret"),
84+
'!' => result.push_str("bang"),
85+
'#' => result.push_str("hash"),
86+
'%' => result.push_str("percent"),
87+
'&' => result.push_str("ampersand"),
88+
'.' => result.push_str("dot"),
89+
',' => result.push_str("comma"),
90+
'/' => result.push_str("slash"),
91+
':' => result.push_str("colon"),
92+
';' => result.push_str("semicolon"),
93+
'"' => result.push_str("dquote"),
94+
'*' => result.push_str("star"),
95+
'+' => result.push_str("plus"),
96+
'-' => result.push_str("minus"),
97+
'@' => result.push_str("at"),
98+
_ => result.push_str(&c.to_lowercase().to_string()),
99+
}
100+
}
101+
102+
for &keyword in &RESERVED_KEYWORDS {
103+
if result == keyword {
104+
result.push_str("__");
105+
break;
106+
}
107+
}
108+
109+
result
110+
}
111+
112+
impl fmt::Display for Table {
113+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114+
if let Some(keyset) = &self.keysets {
115+
write!(f, "#keyset[")?;
116+
for (key_index, key) in keyset.iter().enumerate() {
117+
if key_index > 0 {
118+
write!(f, ", ")?;
119+
}
120+
write!(f, "{}", key)?;
121+
}
122+
write!(f, "]\n")?;
123+
}
124+
125+
write!(f, "{}(\n", self.name)?;
126+
for (column_index, column) in self.columns.iter().enumerate() {
127+
write!(f, " ")?;
128+
if column.unique {
129+
write!(f, "unique ")?;
130+
}
131+
write!(
132+
f,
133+
"{} ",
134+
match column.db_type {
135+
DbColumnType::Int => "int",
136+
DbColumnType::String => "string",
137+
}
138+
)?;
139+
write!(f, "{}: ", column.name)?;
140+
match &column.ql_type {
141+
QlColumnType::Int => write!(f, "int")?,
142+
QlColumnType::String => write!(f, "string")?,
143+
QlColumnType::Custom(name) => write!(f, "@{}", name)?,
144+
}
145+
if column.ql_type_is_ref {
146+
write!(f, " ref")?;
147+
}
148+
if column_index + 1 != self.columns.len() {
149+
write!(f, ",")?;
150+
}
151+
write!(f, "\n")?;
152+
}
153+
write!(f, ");")?;
154+
155+
Ok(())
156+
}
157+
}
158+
159+
impl fmt::Display for Union {
160+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161+
write!(f, "@{} = ", self.name)?;
162+
let mut first = true;
163+
for member in &self.members {
164+
if first {
165+
first = false;
166+
} else {
167+
write!(f, " | ")?;
168+
}
169+
write!(f, "@{}", member)?;
170+
}
171+
Ok(())
172+
}
173+
}
174+
175+
/// Generates the dbscheme by writing the given dbscheme `entries` to the `file`.
176+
pub fn write(
177+
language_name: &str,
178+
file: &mut dyn std::io::Write,
179+
entries: &[Entry],
180+
) -> std::io::Result<()> {
181+
write!(file, "// CodeQL database schema for {}\n", language_name)?;
182+
write!(
183+
file,
184+
"// Automatically generated from the tree-sitter grammar; do not edit\n\n"
185+
)?;
186+
187+
for entry in entries {
188+
match entry {
189+
Entry::Table(table) => write!(file, "{}\n\n", table)?,
190+
Entry::Union(union) => write!(file, "{}\n\n", union)?,
191+
}
192+
}
193+
194+
Ok(())
195+
}

generator/src/language.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use std::path::PathBuf;
2+
3+
pub struct Language {
4+
pub name: String,
5+
pub node_types_path: PathBuf,
6+
pub dbscheme_path: PathBuf,
7+
}

0 commit comments

Comments
 (0)