Repository: proullon/ramsql
Pull request #61 (2022-07-02 16:38:00)

parse CREATE INDEX


Patch:
diff --git a/engine/parser/create.go b/engine/parser/create.go
index a94e8c0..8807465 100644
--- a/engine/parser/create.go
+++ b/engine/parser/create.go
@@ -29,6 +29,30 @@ func (p *parser) parseCreate(tokens []Token) (*Instruction, error) {
 		}
 		createDecl.Add(d)
 		break
+	case IndexToken:
+		d, err := p.parseIndex(tokens)
+		if err != nil {
+			return nil, err
+		}
+		createDecl.Add(d)
+		break
+	case UniqueToken:
+		u, err := p.consumeToken(UniqueToken)
+		if err != nil {
+			return nil, err
+		}
+		// should have index after unique here
+		if !p.hasNext() || tokens[p.index].Token != IndexToken {
+			return nil, fmt.Errorf("expected INDEX after UNIQUE")
+		}
+		d, err := p.parseIndex(tokens)
+		if err != nil {
+			return nil, err
+		}
+		d.Add(u)
+		createDecl.Add(d)
+		break
+
 	default:
 		return nil, fmt.Errorf("Parsing error near <%s>", tokens[p.index].Lexeme)
 	}
@@ -36,6 +60,114 @@ func (p *parser) parseCreate(tokens []Token) (*Instruction, error) {
 	return i, nil
 }
 
+// INDEX index_name ON table_name (col1, col2)
+func (p *parser) parseIndex(tokens []Token) (*Decl, error) {
+	var err error
+	indexDecl := NewDecl(tokens[p.index])
+	p.index++
+
+	// Maybe have "IF NOT EXISTS" here
+	if p.is(IfToken) {
+		ifDecl, err := p.consumeToken(IfToken)
+		if err != nil {
+			return nil, err
+		}
+		indexDecl.Add(ifDecl)
+
+		if p.is(NotToken) {
+			notDecl, err := p.consumeToken(NotToken)
+			if err != nil {
+				return nil, err
+			}
+			ifDecl.Add(notDecl)
+			if !p.is(ExistsToken) {
+				return nil, p.syntaxError()
+			}
+			existsDecl, err := p.consumeToken(ExistsToken)
+			if err != nil {
+				return nil, err
+			}
+			notDecl.Add(existsDecl)
+		}
+	}
+
+	// Now we should found index name
+	nameIndex, err := p.parseAttribute()
+	if err != nil {
+		return nil, p.syntaxError()
+	}
+	indexDecl.Add(nameIndex)
+
+	// ON
+	if !p.hasNext() || tokens[p.index].Token != OnToken {
+		return nil, fmt.Errorf("Expected ON")
+	}
+	p.index++
+
+	// Now we should found table name
+	nameTable, err := p.parseAttribute()
+	if err != nil {
+		return nil, p.syntaxError()
+	}
+	indexDecl.Add(nameTable)
+
+	// Now we should found brackets
+	if !p.hasNext() || tokens[p.index].Token != BracketOpeningToken {
+		return nil, fmt.Errorf("Table name token must be followed by table definition")
+	}
+	p.index++
+
+	for p.index < len(tokens) {
+
+		// New attribute name
+		newAttribute, err := p.parseQuotedToken()
+		if err != nil {
+			return nil, err
+		}
+		indexDecl.Add(newAttribute)
+
+		// Closing bracket ?
+		if tokens[p.index].Token == BracketClosingToken {
+			p.consumeToken(BracketClosingToken)
+			break
+		}
+
+		// All the following tokens until bracket or comma are column constraints.
+		// Column constraints can be listed in any order.
+		for p.isNot(BracketClosingToken, CommaToken) {
+			switch p.cur().Token {
+			case CollateToken: // COLLATE NOCASE
+				collateDecl, err := p.consumeToken(CollateToken)
+				if err != nil {
+					return nil, p.syntaxError()
+				}
+				newAttribute.Add(collateDecl)
+				n, err := p.consumeToken(NocaseToken)
+				if err != nil {
+					return nil, p.syntaxError()
+				}
+				collateDecl.Add(n)
+			default:
+				// Unknown column constraint
+				return nil, p.syntaxError()
+			}
+		}
+
+		// The current token is either closing bracked or comma.
+
+		// Closing bracket means table parsing stops.
+		if tokens[p.index].Token == BracketClosingToken {
+			p.index++
+			break
+		}
+
+		// Comma means continue on next table column.
+		p.index++
+	}
+
+	return indexDecl, nil
+}
+
 func (p *parser) parseTable(tokens []Token) (*Decl, error) {
 	var err error
 	tableDecl := NewDecl(tokens[p.index])
diff --git a/engine/parser/lexer.go b/engine/parser/lexer.go
index fa94ca5..18e4d19 100644
--- a/engine/parser/lexer.go
+++ b/engine/parser/lexer.go
@@ -80,6 +80,9 @@ const (
 	UniqueToken
 	NowToken
 	OffsetToken
+	IndexToken
+	CollateToken
+	NocaseToken
 
 	// Type Token
 
@@ -178,6 +181,10 @@ func (l *lexer) lex(instruction []byte) ([]Token, error) {
 	matchers = append(matchers, l.MatchUniqueToken)
 	matchers = append(matchers, l.MatchNowToken)
 	matchers = append(matchers, l.MatchOffsetToken)
+	matchers = append(matchers, l.MatchIndexToken)
+	matchers = append(matchers, l.MatchOnToken)
+	matchers = append(matchers, l.MatchCollateToken)
+	matchers = append(matchers, l.MatchNocaseToken)
 	// Type Matcher
 	matchers = append(matchers, l.MatchPrimaryToken)
 	matchers = append(matchers, l.MatchKeyToken)
@@ -409,6 +416,18 @@ func (l *lexer) MatchOffsetToken() bool {
 	return l.Match([]byte("offset"), OffsetToken)
 }
 
+func (l *lexer) MatchIndexToken() bool {
+	return l.Match([]byte("index"), IndexToken)
+}
+
+func (l *lexer) MatchCollateToken() bool {
+	return l.Match([]byte("collate"), CollateToken)
+}
+
+func (l *lexer) MatchNocaseToken() bool {
+	return l.Match([]byte("nocase"), NocaseToken)
+}
+
 func (l *lexer) MatchStringToken() bool {
 
 	i := l.pos
diff --git a/engine/parser/parser.go b/engine/parser/parser.go
index 3dc496b..a2f1cbf 100644
--- a/engine/parser/parser.go
+++ b/engine/parser/parser.go
@@ -279,28 +279,28 @@ func (p *parser) parseInsert() (*Instruction, error) {
 
 	for {
 		openingBracketDecl, err := p.consumeToken(BracketOpeningToken)
-	if err != nil {
-		return nil, err
-	}
-		valuesDecl.Add(openingBracketDecl)
-
-	// should be a list of values for specified attributes
-	for {
-		decl, err := p.parseListElement()
 		if err != nil {
 			return nil, err
 		}
+		valuesDecl.Add(openingBracketDecl)
+
+		// should be a list of values for specified attributes
+		for {
+			decl, err := p.parseListElement()
+			if err != nil {
+				return nil, err
+			}
 			openingBracketDecl.Add(decl)
 
-		if p.is(BracketClosingToken) {
-			p.consumeToken(BracketClosingToken)
-			break
-		}
+			if p.is(BracketClosingToken) {
+				p.consumeToken(BracketClosingToken)
+				break
+			}
 
-		_, err = p.consumeToken(CommaToken)
-		if err != nil {
-			return nil, err
-		}
+			_, err = p.consumeToken(CommaToken)
+			if err != nil {
+				return nil, err
+			}
 		}
 
 		if p.is(CommaToken) {


Test patch:
diff --git a/engine/parser/parser_test.go b/engine/parser/parser_test.go
index 96eafce..9840e85 100644
--- a/engine/parser/parser_test.go
+++ b/engine/parser/parser_test.go
@@ -262,6 +262,19 @@ func TestNow(t *testing.T) {
 	parse(query, 1, t)
 }
 
+func TestIndex(t *testing.T) {
+	queries := []string{
+		`CREATE INDEX index_name ON table_name (col1, col2)`,
+		`CREATE UNIQUE INDEX index_name ON table_name (col1, col2)`,
+		`CREATE UNIQUE INDEX IF NOT EXISTS index_name ON table_name (col1, col2)`,
+		`CREATE UNIQUE INDEX IF NOT EXISTS index_name ON table_name (col1, col2 COLLATE NOCASE)`,
+	}
+
+	for _, q := range queries {
+		parse(q, 1, t)
+	}
+}
+
 /*
 func TestForeignKey(t *testing.T) {
 	queries := []string{