Skip to content

Commit

Permalink
Merge pull request #8281 from dolthub/db/dolt-ci-events-init
Browse files Browse the repository at this point in the history
Db/dolt ci events init
  • Loading branch information
coffeegoddd authored Aug 16, 2024
2 parents 6674209 + 377457d commit 413088a
Show file tree
Hide file tree
Showing 12 changed files with 554 additions and 964 deletions.
23 changes: 23 additions & 0 deletions go/libraries/doltcore/doltdb/dolt_ci_table_creator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package doltdb

import (
"context"
)

type DoltCITableCreator interface {
CreateTable(ctx context.Context, rv RootValue) (RootValue, error)
}
228 changes: 228 additions & 0 deletions go/libraries/doltcore/doltdb/dolt_ci_table_fk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package doltdb

import (
"context"
"fmt"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/go-mysql-server/sql"
"sort"
"strings"
)

func CreateDoltCITableForeignKey(
ctx context.Context,
root RootValue,
tbl *Table,
sch schema.Schema,
sqlFk sql.ForeignKeyConstraint,
onUpdateRefAction, onDeleteRefAction ForeignKeyReferentialAction,
schemaName string) (ForeignKey, error) {
if !sqlFk.IsResolved {
return ForeignKey{
Name: sqlFk.Name,
TableName: sqlFk.Table,
TableIndex: "",
TableColumns: nil,
ReferencedTableName: sqlFk.ParentTable,
ReferencedTableIndex: "",
ReferencedTableColumns: nil,
OnUpdate: onUpdateRefAction,
OnDelete: onDeleteRefAction,
UnresolvedFKDetails: UnresolvedFKDetails{
TableColumns: sqlFk.Columns,
ReferencedTableColumns: sqlFk.ParentColumns,
},
}, nil
}
colTags := make([]uint64, len(sqlFk.Columns))
for i, col := range sqlFk.Columns {
tableCol, ok := sch.GetAllCols().GetByNameCaseInsensitive(col)
if !ok {
return ForeignKey{}, fmt.Errorf("table `%s` does not have column `%s`", sqlFk.Table, col)
}
colTags[i] = tableCol.Tag
}

var refTbl *Table
var refSch schema.Schema
if sqlFk.IsSelfReferential() {
refTbl = tbl
refSch = sch
} else {
var ok bool
var err error
// TODO: the parent table can be in another schema

refTbl, _, ok, err = GetTableInsensitive(ctx, root, TableName{Name: sqlFk.ParentTable, Schema: schemaName})
if err != nil {
return ForeignKey{}, err
}
if !ok {
return ForeignKey{}, fmt.Errorf("referenced table `%s` does not exist", sqlFk.ParentTable)
}
refSch, err = refTbl.GetSchema(ctx)
if err != nil {
return ForeignKey{}, err
}
}

refColTags := make([]uint64, len(sqlFk.ParentColumns))
for i, name := range sqlFk.ParentColumns {
refCol, ok := refSch.GetAllCols().GetByNameCaseInsensitive(name)
if !ok {
return ForeignKey{}, fmt.Errorf("table `%s` does not have column `%s`", sqlFk.ParentTable, name)
}
refColTags[i] = refCol.Tag
}

var tableIndexName, refTableIndexName string
tableIndex, ok, err := FindIndexWithPrefix(sch, sqlFk.Columns)
if err != nil {
return ForeignKey{}, err
}
// Use secondary index if found; otherwise it will use empty string, indicating primary key
if ok {
tableIndexName = tableIndex.Name()
}
refTableIndex, ok, err := FindIndexWithPrefix(refSch, sqlFk.ParentColumns)
if err != nil {
return ForeignKey{}, err
}
// Use secondary index if found; otherwise it will use empty string, indicating primary key
if ok {
refTableIndexName = refTableIndex.Name()
}
return ForeignKey{
Name: sqlFk.Name,
TableName: sqlFk.Table,
TableIndex: tableIndexName,
TableColumns: colTags,
ReferencedTableName: sqlFk.ParentTable,
ReferencedTableIndex: refTableIndexName,
ReferencedTableColumns: refColTags,
OnUpdate: onUpdateRefAction,
OnDelete: onDeleteRefAction,
UnresolvedFKDetails: UnresolvedFKDetails{
TableColumns: sqlFk.Columns,
ReferencedTableColumns: sqlFk.ParentColumns,
},
}, nil
}

func FindIndexWithPrefix(sch schema.Schema, prefixCols []string) (schema.Index, bool, error) {
type idxWithLen struct {
schema.Index
colLen int
}

prefixCols = lowercaseSlice(prefixCols)
indexes := sch.Indexes().AllIndexes()
colLen := len(prefixCols)
var indexesWithLen []idxWithLen
for _, idx := range indexes {
idxCols := lowercaseSlice(idx.ColumnNames())
if ok, prefixCount := colsAreIndexSubset(prefixCols, idxCols); ok && prefixCount == colLen {
indexesWithLen = append(indexesWithLen, idxWithLen{idx, len(idxCols)})
}
}
if len(indexesWithLen) == 0 {
return nil, false, nil
}

sort.Slice(indexesWithLen, func(i, j int) bool {
idxI := indexesWithLen[i]
idxJ := indexesWithLen[j]
if idxI.colLen == colLen && idxJ.colLen != colLen {
return true
} else if idxI.colLen != colLen && idxJ.colLen == colLen {
return false
} else if idxI.colLen != idxJ.colLen {
return idxI.colLen > idxJ.colLen
} else if idxI.IsUnique() != idxJ.IsUnique() {
// prefer unique indexes
return idxI.IsUnique() && !idxJ.IsUnique()
} else {
return idxI.Index.Name() < idxJ.Index.Name()
}
})
sortedIndexes := make([]schema.Index, len(indexesWithLen))
for i := 0; i < len(sortedIndexes); i++ {
sortedIndexes[i] = indexesWithLen[i].Index
}
return sortedIndexes[0], true, nil
}

func colsAreIndexSubset(cols, indexCols []string) (ok bool, prefixCount int) {
if len(cols) > len(indexCols) {
return false, 0
}

visitedIndexCols := make([]bool, len(indexCols))
for _, expr := range cols {
found := false
for j, indexExpr := range indexCols {
if visitedIndexCols[j] {
continue
}
if expr == indexExpr {
visitedIndexCols[j] = true
found = true
break
}
}
if !found {
return false, 0
}
}

// This checks the length of the prefix by checking how many true booleans are encountered before the first false
for i, visitedCol := range visitedIndexCols {
if visitedCol {
continue
}
return true, i
}

return true, len(cols)
}

func lowercaseSlice(strs []string) []string {
newStrs := make([]string, len(strs))
for i, str := range strs {
newStrs[i] = strings.ToLower(str)
}
return newStrs
}

func ParseFkReferentialAction(refOp sql.ForeignKeyReferentialAction) (ForeignKeyReferentialAction, error) {
switch refOp {
case sql.ForeignKeyReferentialAction_DefaultAction:
return ForeignKeyReferentialAction_DefaultAction, nil
case sql.ForeignKeyReferentialAction_Restrict:
return ForeignKeyReferentialAction_Restrict, nil
case sql.ForeignKeyReferentialAction_Cascade:
return ForeignKeyReferentialAction_Cascade, nil
case sql.ForeignKeyReferentialAction_NoAction:
return ForeignKeyReferentialAction_NoAction, nil
case sql.ForeignKeyReferentialAction_SetNull:
return ForeignKeyReferentialAction_SetNull, nil
case sql.ForeignKeyReferentialAction_SetDefault:
return ForeignKeyReferentialAction_DefaultAction, sql.ErrForeignKeySetDefault.New()
default:
return ForeignKeyReferentialAction_DefaultAction, fmt.Errorf("unknown foreign key referential action: %v", refOp)
}
}
46 changes: 46 additions & 0 deletions go/libraries/doltcore/doltdb/dolt_ci_tables_creator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package doltdb

import (
"context"
)

type DoltCITablesCreator interface {
CreateTables(ctx context.Context, rv RootValue) (RootValue, error)
}

type doltCITablesCreator struct {
workflowsTC DoltCITableCreator
workflowEventsTC DoltCITableCreator
}

func NewDoltCITablesCreator(dbName string) *doltCITablesCreator {
return &doltCITablesCreator{
workflowsTC: NewDoltCIWorkflowsTableCreator(),
workflowEventsTC: NewDoltCIWorkflowEventsTableCreator(dbName),
}
}

func (d doltCITablesCreator) CreateTables(ctx context.Context, rv RootValue) (RootValue, error) {
rv, err := d.workflowsTC.CreateTable(ctx, rv)
if err != nil {
return nil, err
}

return d.workflowEventsTC.CreateTable(ctx, rv)
}

var _ DoltCITablesCreator = &doltCITablesCreator{}
Loading

0 comments on commit 413088a

Please sign in to comment.