mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-01-24 22:32:42 +02:00
Merge branch 'master' into develop
This commit is contained in:
commit
5a5125383a
@ -402,6 +402,8 @@ func (dao *Dao) saveViewCollection(newCollection, oldCollection *models.Collecti
|
|||||||
// currently we don't support non-string model ids
|
// currently we don't support non-string model ids
|
||||||
// (see https://github.com/pocketbase/pocketbase/issues/3110).
|
// (see https://github.com/pocketbase/pocketbase/issues/3110).
|
||||||
func (dao *Dao) normalizeViewQueryId(query string) (string, error) {
|
func (dao *Dao) normalizeViewQueryId(query string) (string, error) {
|
||||||
|
query = strings.Trim(strings.TrimSpace(query), ";")
|
||||||
|
|
||||||
parsed, err := dao.parseQueryToFields(query)
|
parsed, err := dao.parseQueryToFields(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
21
daos/view.go
21
daos/view.go
@ -43,10 +43,10 @@ func (dao *Dao) SaveView(name string, selectQuery string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmed := strings.Trim(selectQuery, ";")
|
selectQuery = strings.Trim(strings.TrimSpace(selectQuery), ";")
|
||||||
|
|
||||||
// try to eagerly detect multiple inline statements
|
// try to eagerly detect multiple inline statements
|
||||||
tk := tokenizer.NewFromString(trimmed)
|
tk := tokenizer.NewFromString(selectQuery)
|
||||||
tk.Separators(';')
|
tk.Separators(';')
|
||||||
if queryParts, _ := tk.ScanAll(); len(queryParts) > 1 {
|
if queryParts, _ := tk.ScanAll(); len(queryParts) > 1 {
|
||||||
return errors.New("multiple statements are not supported")
|
return errors.New("multiple statements are not supported")
|
||||||
@ -56,7 +56,7 @@ func (dao *Dao) SaveView(name string, selectQuery string) error {
|
|||||||
//
|
//
|
||||||
// note: the query is wrapped in a secondary SELECT as a rudimentary
|
// note: the query is wrapped in a secondary SELECT as a rudimentary
|
||||||
// measure to discourage multiple inline sql statements execution.
|
// measure to discourage multiple inline sql statements execution.
|
||||||
viewQuery := fmt.Sprintf("CREATE VIEW {{%s}} AS SELECT * FROM (%s)", name, trimmed)
|
viewQuery := fmt.Sprintf("CREATE VIEW {{%s}} AS SELECT * FROM (%s)", name, selectQuery)
|
||||||
if _, err := txDao.DB().NewQuery(viewQuery).Execute(); err != nil {
|
if _, err := txDao.DB().NewQuery(viewQuery).Execute(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -458,7 +458,7 @@ type identifiersParser struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *identifiersParser) parse(selectQuery string) error {
|
func (p *identifiersParser) parse(selectQuery string) error {
|
||||||
str := strings.Trim(selectQuery, ";")
|
str := strings.Trim(strings.TrimSpace(selectQuery), ";")
|
||||||
str = joinReplaceRegex.ReplaceAllString(str, " _join_ ")
|
str = joinReplaceRegex.ReplaceAllString(str, " _join_ ")
|
||||||
str = discardReplaceRegex.ReplaceAllString(str, " _discard_ ")
|
str = discardReplaceRegex.ReplaceAllString(str, " _discard_ ")
|
||||||
str = commentsReplaceRegex.ReplaceAllString(str, "")
|
str = commentsReplaceRegex.ReplaceAllString(str, "")
|
||||||
@ -599,13 +599,20 @@ func identifierFromParts(parts []string) (identifier, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.original = trimRawIdentifier(result.original)
|
result.original = trimRawIdentifier(result.original)
|
||||||
result.alias = trimRawIdentifier(result.alias)
|
|
||||||
|
// we trim the single quote even though it is not a valid column quote character
|
||||||
|
// because SQLite allows it if the context expects an identifier and not string literal
|
||||||
|
// (https://www.sqlite.org/lang_keywords.html)
|
||||||
|
result.alias = trimRawIdentifier(result.alias, "'")
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimRawIdentifier(rawIdentifier string) string {
|
func trimRawIdentifier(rawIdentifier string, extraTrimChars ...string) string {
|
||||||
const trimChars = "`\"[];"
|
trimChars := "`\"[];"
|
||||||
|
if len(extraTrimChars) > 0 {
|
||||||
|
trimChars += strings.Join(extraTrimChars, "")
|
||||||
|
}
|
||||||
|
|
||||||
parts := strings.Split(rawIdentifier, ".")
|
parts := strings.Split(rawIdentifier, ".")
|
||||||
|
|
||||||
|
@ -147,34 +147,33 @@ func TestSaveView(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
err := app.Dao().SaveView(s.viewName, s.query)
|
t.Run(s.scenarioName, func(t *testing.T) {
|
||||||
|
err := app.Dao().SaveView(s.viewName, s.query)
|
||||||
|
|
||||||
hasErr := err != nil
|
hasErr := err != nil
|
||||||
if hasErr != s.expectError {
|
if hasErr != s.expectError {
|
||||||
t.Errorf("[%s] Expected hasErr %v, got %v (%v)", s.scenarioName, s.expectError, hasErr, err)
|
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasErr {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
infoRows, err := app.Dao().TableInfo(s.viewName)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%s] Failed to fetch table info for %s: %v", s.scenarioName, s.viewName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(s.expectColumns) != len(infoRows) {
|
|
||||||
t.Errorf("[%s] Expected %d columns, got %d", s.scenarioName, len(s.expectColumns), len(infoRows))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, row := range infoRows {
|
|
||||||
if !list.ExistInSlice(row.Name, s.expectColumns) {
|
|
||||||
t.Errorf("[%s] Missing %q column in %v", s.scenarioName, row.Name, s.expectColumns)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if hasErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
infoRows, err := app.Dao().TableInfo(s.viewName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch table info for %s: %v", s.viewName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.expectColumns) != len(infoRows) {
|
||||||
|
t.Fatalf("Expected %d columns, got %d", len(s.expectColumns), len(infoRows))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range infoRows {
|
||||||
|
if !list.ExistInSlice(row.Name, s.expectColumns) {
|
||||||
|
t.Fatalf("Missing %q column in %v", row.Name, s.expectColumns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureNoTempViews(app, t)
|
ensureNoTempViews(app, t)
|
||||||
@ -272,24 +271,26 @@ func TestCreateViewSchema(t *testing.T) {
|
|||||||
"datetime",
|
"datetime",
|
||||||
"json",
|
"json",
|
||||||
"rel_one",
|
"rel_one",
|
||||||
"rel_many"
|
"rel_many",
|
||||||
|
'single_quoted_custom_literal' as 'single_quoted_column'
|
||||||
from demo1
|
from demo1
|
||||||
`,
|
`,
|
||||||
false,
|
false,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"text": schema.FieldTypeText,
|
"text": schema.FieldTypeText,
|
||||||
"bool": schema.FieldTypeBool,
|
"bool": schema.FieldTypeBool,
|
||||||
"url": schema.FieldTypeUrl,
|
"url": schema.FieldTypeUrl,
|
||||||
"select_one": schema.FieldTypeSelect,
|
"select_one": schema.FieldTypeSelect,
|
||||||
"select_many": schema.FieldTypeSelect,
|
"select_many": schema.FieldTypeSelect,
|
||||||
"file_one": schema.FieldTypeFile,
|
"file_one": schema.FieldTypeFile,
|
||||||
"file_many": schema.FieldTypeFile,
|
"file_many": schema.FieldTypeFile,
|
||||||
"number_alias": schema.FieldTypeNumber,
|
"number_alias": schema.FieldTypeNumber,
|
||||||
"email": schema.FieldTypeEmail,
|
"email": schema.FieldTypeEmail,
|
||||||
"datetime": schema.FieldTypeDate,
|
"datetime": schema.FieldTypeDate,
|
||||||
"json": schema.FieldTypeJson,
|
"json": schema.FieldTypeJson,
|
||||||
"rel_one": schema.FieldTypeRelation,
|
"rel_one": schema.FieldTypeRelation,
|
||||||
"rel_many": schema.FieldTypeRelation,
|
"rel_many": schema.FieldTypeRelation,
|
||||||
|
"single_quoted_column": schema.FieldTypeJson,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user