mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-25 17:44:32 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			493 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			493 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 The Xorm Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package xorm
 | |
| 
 | |
| import (
 | |
| 	"database/sql"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"xorm.io/core"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	sqlite3ReservedWords = map[string]bool{
 | |
| 		"ABORT":             true,
 | |
| 		"ACTION":            true,
 | |
| 		"ADD":               true,
 | |
| 		"AFTER":             true,
 | |
| 		"ALL":               true,
 | |
| 		"ALTER":             true,
 | |
| 		"ANALYZE":           true,
 | |
| 		"AND":               true,
 | |
| 		"AS":                true,
 | |
| 		"ASC":               true,
 | |
| 		"ATTACH":            true,
 | |
| 		"AUTOINCREMENT":     true,
 | |
| 		"BEFORE":            true,
 | |
| 		"BEGIN":             true,
 | |
| 		"BETWEEN":           true,
 | |
| 		"BY":                true,
 | |
| 		"CASCADE":           true,
 | |
| 		"CASE":              true,
 | |
| 		"CAST":              true,
 | |
| 		"CHECK":             true,
 | |
| 		"COLLATE":           true,
 | |
| 		"COLUMN":            true,
 | |
| 		"COMMIT":            true,
 | |
| 		"CONFLICT":          true,
 | |
| 		"CONSTRAINT":        true,
 | |
| 		"CREATE":            true,
 | |
| 		"CROSS":             true,
 | |
| 		"CURRENT_DATE":      true,
 | |
| 		"CURRENT_TIME":      true,
 | |
| 		"CURRENT_TIMESTAMP": true,
 | |
| 		"DATABASE":          true,
 | |
| 		"DEFAULT":           true,
 | |
| 		"DEFERRABLE":        true,
 | |
| 		"DEFERRED":          true,
 | |
| 		"DELETE":            true,
 | |
| 		"DESC":              true,
 | |
| 		"DETACH":            true,
 | |
| 		"DISTINCT":          true,
 | |
| 		"DROP":              true,
 | |
| 		"EACH":              true,
 | |
| 		"ELSE":              true,
 | |
| 		"END":               true,
 | |
| 		"ESCAPE":            true,
 | |
| 		"EXCEPT":            true,
 | |
| 		"EXCLUSIVE":         true,
 | |
| 		"EXISTS":            true,
 | |
| 		"EXPLAIN":           true,
 | |
| 		"FAIL":              true,
 | |
| 		"FOR":               true,
 | |
| 		"FOREIGN":           true,
 | |
| 		"FROM":              true,
 | |
| 		"FULL":              true,
 | |
| 		"GLOB":              true,
 | |
| 		"GROUP":             true,
 | |
| 		"HAVING":            true,
 | |
| 		"IF":                true,
 | |
| 		"IGNORE":            true,
 | |
| 		"IMMEDIATE":         true,
 | |
| 		"IN":                true,
 | |
| 		"INDEX":             true,
 | |
| 		"INDEXED":           true,
 | |
| 		"INITIALLY":         true,
 | |
| 		"INNER":             true,
 | |
| 		"INSERT":            true,
 | |
| 		"INSTEAD":           true,
 | |
| 		"INTERSECT":         true,
 | |
| 		"INTO":              true,
 | |
| 		"IS":                true,
 | |
| 		"ISNULL":            true,
 | |
| 		"JOIN":              true,
 | |
| 		"KEY":               true,
 | |
| 		"LEFT":              true,
 | |
| 		"LIKE":              true,
 | |
| 		"LIMIT":             true,
 | |
| 		"MATCH":             true,
 | |
| 		"NATURAL":           true,
 | |
| 		"NO":                true,
 | |
| 		"NOT":               true,
 | |
| 		"NOTNULL":           true,
 | |
| 		"NULL":              true,
 | |
| 		"OF":                true,
 | |
| 		"OFFSET":            true,
 | |
| 		"ON":                true,
 | |
| 		"OR":                true,
 | |
| 		"ORDER":             true,
 | |
| 		"OUTER":             true,
 | |
| 		"PLAN":              true,
 | |
| 		"PRAGMA":            true,
 | |
| 		"PRIMARY":           true,
 | |
| 		"QUERY":             true,
 | |
| 		"RAISE":             true,
 | |
| 		"RECURSIVE":         true,
 | |
| 		"REFERENCES":        true,
 | |
| 		"REGEXP":            true,
 | |
| 		"REINDEX":           true,
 | |
| 		"RELEASE":           true,
 | |
| 		"RENAME":            true,
 | |
| 		"REPLACE":           true,
 | |
| 		"RESTRICT":          true,
 | |
| 		"RIGHT":             true,
 | |
| 		"ROLLBACK":          true,
 | |
| 		"ROW":               true,
 | |
| 		"SAVEPOINT":         true,
 | |
| 		"SELECT":            true,
 | |
| 		"SET":               true,
 | |
| 		"TABLE":             true,
 | |
| 		"TEMP":              true,
 | |
| 		"TEMPORARY":         true,
 | |
| 		"THEN":              true,
 | |
| 		"TO":                true,
 | |
| 		"TRANSACTI":         true,
 | |
| 		"TRIGGER":           true,
 | |
| 		"UNION":             true,
 | |
| 		"UNIQUE":            true,
 | |
| 		"UPDATE":            true,
 | |
| 		"USING":             true,
 | |
| 		"VACUUM":            true,
 | |
| 		"VALUES":            true,
 | |
| 		"VIEW":              true,
 | |
| 		"VIRTUAL":           true,
 | |
| 		"WHEN":              true,
 | |
| 		"WHERE":             true,
 | |
| 		"WITH":              true,
 | |
| 		"WITHOUT":           true,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| type sqlite3 struct {
 | |
| 	core.Base
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error {
 | |
| 	return db.Base.Init(d, db, uri, drivername, dataSourceName)
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) SqlType(c *core.Column) string {
 | |
| 	switch t := c.SQLType.Name; t {
 | |
| 	case core.Bool:
 | |
| 		if c.Default == "true" {
 | |
| 			c.Default = "1"
 | |
| 		} else if c.Default == "false" {
 | |
| 			c.Default = "0"
 | |
| 		}
 | |
| 		return core.Integer
 | |
| 	case core.Date, core.DateTime, core.TimeStamp, core.Time:
 | |
| 		return core.DateTime
 | |
| 	case core.TimeStampz:
 | |
| 		return core.Text
 | |
| 	case core.Char, core.Varchar, core.NVarchar, core.TinyText,
 | |
| 		core.Text, core.MediumText, core.LongText, core.Json:
 | |
| 		return core.Text
 | |
| 	case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt:
 | |
| 		return core.Integer
 | |
| 	case core.Float, core.Double, core.Real:
 | |
| 		return core.Real
 | |
| 	case core.Decimal, core.Numeric:
 | |
| 		return core.Numeric
 | |
| 	case core.TinyBlob, core.Blob, core.MediumBlob, core.LongBlob, core.Bytea, core.Binary, core.VarBinary:
 | |
| 		return core.Blob
 | |
| 	case core.Serial, core.BigSerial:
 | |
| 		c.IsPrimaryKey = true
 | |
| 		c.IsAutoIncrement = true
 | |
| 		c.Nullable = false
 | |
| 		return core.Integer
 | |
| 	default:
 | |
| 		return t
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) FormatBytes(bs []byte) string {
 | |
| 	return fmt.Sprintf("X'%x'", bs)
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) SupportInsertMany() bool {
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) IsReserved(name string) bool {
 | |
| 	_, ok := sqlite3ReservedWords[name]
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) Quote(name string) string {
 | |
| 	return "`" + name + "`"
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) AutoIncrStr() string {
 | |
| 	return "AUTOINCREMENT"
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) SupportEngine() bool {
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) SupportCharset() bool {
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) IndexOnTable() bool {
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) IndexCheckSql(tableName, idxName string) (string, []interface{}) {
 | |
| 	args := []interface{}{idxName}
 | |
| 	return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) TableCheckSql(tableName string) (string, []interface{}) {
 | |
| 	args := []interface{}{tableName}
 | |
| 	return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string {
 | |
| 	// var unique string
 | |
| 	quote := db.Quote
 | |
| 	idxName := index.Name
 | |
| 
 | |
| 	if !strings.HasPrefix(idxName, "UQE_") &&
 | |
| 		!strings.HasPrefix(idxName, "IDX_") {
 | |
| 		if index.Type == core.UniqueType {
 | |
| 			idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
 | |
| 		} else {
 | |
| 			idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
 | |
| 		}
 | |
| 	}
 | |
| 	return fmt.Sprintf("DROP INDEX %v", quote(idxName))
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) ForUpdateSql(query string) string {
 | |
| 	return query
 | |
| }
 | |
| 
 | |
| /*func (db *sqlite3) ColumnCheckSql(tableName, colName string) (string, []interface{}) {
 | |
| 	args := []interface{}{tableName}
 | |
| 	sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))"
 | |
| 	return sql, args
 | |
| }*/
 | |
| 
 | |
| func (db *sqlite3) IsColumnExist(tableName, colName string) (bool, error) {
 | |
| 	args := []interface{}{tableName}
 | |
| 	query := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))"
 | |
| 	db.LogSQL(query, args)
 | |
| 	rows, err := db.DB().Query(query, args...)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	defer rows.Close()
 | |
| 
 | |
| 	if rows.Next() {
 | |
| 		return true, nil
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| // splitColStr splits a sqlite col strings as fields
 | |
| func splitColStr(colStr string) []string {
 | |
| 	colStr = strings.TrimSpace(colStr)
 | |
| 	var results = make([]string, 0, 10)
 | |
| 	var lastIdx int
 | |
| 	var hasC, hasQuote bool
 | |
| 	for i, c := range colStr {
 | |
| 		if c == ' ' && !hasQuote {
 | |
| 			if hasC {
 | |
| 				results = append(results, colStr[lastIdx:i])
 | |
| 				hasC = false
 | |
| 			}
 | |
| 		} else {
 | |
| 			if c == '\'' {
 | |
| 				hasQuote = !hasQuote
 | |
| 			}
 | |
| 			if !hasC {
 | |
| 				lastIdx = i
 | |
| 			}
 | |
| 			hasC = true
 | |
| 			if i == len(colStr)-1 {
 | |
| 				results = append(results, colStr[lastIdx:i+1])
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return results
 | |
| }
 | |
| 
 | |
| func parseString(colStr string) (*core.Column, error) {
 | |
| 	fields := splitColStr(colStr)
 | |
| 	col := new(core.Column)
 | |
| 	col.Indexes = make(map[string]int)
 | |
| 	col.Nullable = true
 | |
| 	col.DefaultIsEmpty = true
 | |
| 
 | |
| 	for idx, field := range fields {
 | |
| 		if idx == 0 {
 | |
| 			col.Name = strings.Trim(strings.Trim(field, "`[] "), `"`)
 | |
| 			continue
 | |
| 		} else if idx == 1 {
 | |
| 			col.SQLType = core.SQLType{Name: field, DefaultLength: 0, DefaultLength2: 0}
 | |
| 			continue
 | |
| 		}
 | |
| 		switch field {
 | |
| 		case "PRIMARY":
 | |
| 			col.IsPrimaryKey = true
 | |
| 		case "AUTOINCREMENT":
 | |
| 			col.IsAutoIncrement = true
 | |
| 		case "NULL":
 | |
| 			if fields[idx-1] == "NOT" {
 | |
| 				col.Nullable = false
 | |
| 			} else {
 | |
| 				col.Nullable = true
 | |
| 			}
 | |
| 		case "DEFAULT":
 | |
| 			col.Default = fields[idx+1]
 | |
| 			col.DefaultIsEmpty = false
 | |
| 		}
 | |
| 	}
 | |
| 	return col, nil
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
 | |
| 	args := []interface{}{tableName}
 | |
| 	s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?"
 | |
| 	db.LogSQL(s, args)
 | |
| 	rows, err := db.DB().Query(s, args...)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	defer rows.Close()
 | |
| 
 | |
| 	var name string
 | |
| 	for rows.Next() {
 | |
| 		err = rows.Scan(&name)
 | |
| 		if err != nil {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 		break
 | |
| 	}
 | |
| 
 | |
| 	if name == "" {
 | |
| 		return nil, nil, errors.New("no table named " + tableName)
 | |
| 	}
 | |
| 
 | |
| 	nStart := strings.Index(name, "(")
 | |
| 	nEnd := strings.LastIndex(name, ")")
 | |
| 	reg := regexp.MustCompile(`[^\(,\)]*(\([^\(]*\))?`)
 | |
| 	colCreates := reg.FindAllString(name[nStart+1:nEnd], -1)
 | |
| 	cols := make(map[string]*core.Column)
 | |
| 	colSeq := make([]string, 0)
 | |
| 
 | |
| 	for _, colStr := range colCreates {
 | |
| 		reg = regexp.MustCompile(`,\s`)
 | |
| 		colStr = reg.ReplaceAllString(colStr, ",")
 | |
| 		if strings.HasPrefix(strings.TrimSpace(colStr), "PRIMARY KEY") {
 | |
| 			parts := strings.Split(strings.TrimSpace(colStr), "(")
 | |
| 			if len(parts) == 2 {
 | |
| 				pkCols := strings.Split(strings.TrimRight(strings.TrimSpace(parts[1]), ")"), ",")
 | |
| 				for _, pk := range pkCols {
 | |
| 					if col, ok := cols[strings.Trim(strings.TrimSpace(pk), "`")]; ok {
 | |
| 						col.IsPrimaryKey = true
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		col, err := parseString(colStr)
 | |
| 		if err != nil {
 | |
| 			return colSeq, cols, err
 | |
| 		}
 | |
| 
 | |
| 		cols[col.Name] = col
 | |
| 		colSeq = append(colSeq, col.Name)
 | |
| 	}
 | |
| 	return colSeq, cols, nil
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) GetTables() ([]*core.Table, error) {
 | |
| 	args := []interface{}{}
 | |
| 	s := "SELECT name FROM sqlite_master WHERE type='table'"
 | |
| 	db.LogSQL(s, args)
 | |
| 
 | |
| 	rows, err := db.DB().Query(s, args...)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer rows.Close()
 | |
| 
 | |
| 	tables := make([]*core.Table, 0)
 | |
| 	for rows.Next() {
 | |
| 		table := core.NewEmptyTable()
 | |
| 		err = rows.Scan(&table.Name)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if table.Name == "sqlite_sequence" {
 | |
| 			continue
 | |
| 		}
 | |
| 		tables = append(tables, table)
 | |
| 	}
 | |
| 	return tables, nil
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) {
 | |
| 	args := []interface{}{tableName}
 | |
| 	s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?"
 | |
| 	db.LogSQL(s, args)
 | |
| 
 | |
| 	rows, err := db.DB().Query(s, args...)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer rows.Close()
 | |
| 
 | |
| 	indexes := make(map[string]*core.Index, 0)
 | |
| 	for rows.Next() {
 | |
| 		var tmpSQL sql.NullString
 | |
| 		err = rows.Scan(&tmpSQL)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		if !tmpSQL.Valid {
 | |
| 			continue
 | |
| 		}
 | |
| 		sql := tmpSQL.String
 | |
| 
 | |
| 		index := new(core.Index)
 | |
| 		nNStart := strings.Index(sql, "INDEX")
 | |
| 		nNEnd := strings.Index(sql, "ON")
 | |
| 		if nNStart == -1 || nNEnd == -1 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
 | |
| 		var isRegular bool
 | |
| 		if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
 | |
| 			index.Name = indexName[5+len(tableName):]
 | |
| 			isRegular = true
 | |
| 		} else {
 | |
| 			index.Name = indexName
 | |
| 		}
 | |
| 
 | |
| 		if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") {
 | |
| 			index.Type = core.UniqueType
 | |
| 		} else {
 | |
| 			index.Type = core.IndexType
 | |
| 		}
 | |
| 
 | |
| 		nStart := strings.Index(sql, "(")
 | |
| 		nEnd := strings.Index(sql, ")")
 | |
| 		colIndexes := strings.Split(sql[nStart+1:nEnd], ",")
 | |
| 
 | |
| 		index.Cols = make([]string, 0)
 | |
| 		for _, col := range colIndexes {
 | |
| 			index.Cols = append(index.Cols, strings.Trim(col, "` []"))
 | |
| 		}
 | |
| 		index.IsRegular = isRegular
 | |
| 		indexes[index.Name] = index
 | |
| 	}
 | |
| 
 | |
| 	return indexes, nil
 | |
| }
 | |
| 
 | |
| func (db *sqlite3) Filters() []core.Filter {
 | |
| 	return []core.Filter{&core.IdFilter{}}
 | |
| }
 | |
| 
 | |
| type sqlite3Driver struct {
 | |
| }
 | |
| 
 | |
| func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
 | |
| 	if strings.Contains(dataSourceName, "?") {
 | |
| 		dataSourceName = dataSourceName[:strings.Index(dataSourceName, "?")]
 | |
| 	}
 | |
| 
 | |
| 	return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil
 | |
| }
 |