diff --git a/kernel/av/attribute_view.go b/kernel/av/attribute_view.go
new file mode 100644
index 000000000..4244d9047
--- /dev/null
+++ b/kernel/av/attribute_view.go
@@ -0,0 +1,134 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Package av 是属性视图相关的实现。
+package av
+
+import (
+ "database/sql"
+ "path/filepath"
+ "strings"
+
+ "github.com/88250/gulu"
+ "github.com/siyuan-note/filelock"
+ "github.com/siyuan-note/logging"
+ "github.com/siyuan-note/siyuan/kernel/util"
+)
+
+// AttributeView 描述了属性视图的结构。
+type AttributeView struct {
+ ID string `json:"id"` // 属性视图 ID
+ Columns []Column `json:"columns"` // 表格列名
+ Rows [][]string `json:"rows"` // 表格行记录
+
+ Projections []string `json:"projections"` // 显示的列名,SELECT *
+ Filters []*AttributeViewFilter `json:"filters"` // 过滤规则,WHERE ...
+ Sorts []*AttributeViewSort `json:"sorts"` // 排序规则,ORDER BY ...
+
+}
+
+func (av *AttributeView) GetColumnNames() (ret []string) {
+ ret = []string{}
+ for _, column := range av.Columns {
+ ret = append(ret, column.Name())
+ }
+ return
+}
+
+type AttributeViewFilter struct {
+ Column string `json:"column"`
+ Operator string `json:"operator"`
+ Value string `json:"value"`
+}
+
+type AttributeViewSort struct {
+ Column string `json:"column"`
+ Order string `json:"order"`
+}
+
+// SyncAttributeViewTableFromJSON 从 JSON 文件同步属性视图表,用于数据同步后将属性视图 JSON 文件同步到数据库。
+func SyncAttributeViewTableFromJSON(tableID string) (err error) {
+ avJSONPath := getAttributeViewJSONPath(tableID)
+ data, err := filelock.ReadFile(avJSONPath)
+ if nil != err {
+ logging.LogErrorf("read attribute view table failed: %s", err)
+ return
+ }
+
+ var attributeView AttributeView
+ if err = gulu.JSON.UnmarshalJSON(data, &attributeView); nil != err {
+ logging.LogErrorf("unmarshal attribute view table failed: %s", err)
+ return
+ }
+
+ return
+}
+
+// SyncAttributeViewTableToJSON 同步属性视图表到 JSON 文件,用于将数据库中的属性视图持久化到 JSON 文件中。
+func SyncAttributeViewTableToJSON(av *AttributeView) (err error) {
+ data, err := gulu.JSON.MarshalJSON(av)
+ if nil != err {
+ logging.LogErrorf("marshal attribute view table [%s] failed: %s", av.ID, err)
+ return
+ }
+
+ avJSONPath := getAttributeViewJSONPath(av.ID)
+ if err = filelock.WriteFile(avJSONPath, data); nil != err {
+ logging.LogErrorf("save attribute view table [%s] failed: %s", av.ID, err)
+ return
+ }
+ return
+}
+
+func getAttributeViewJSONPath(tableID string) string {
+ return filepath.Join(util.DataDir, "storage", "av", tableID+".json")
+}
+
+func dropAttributeViewTableColumn(db *sql.DB, tableID string, column string) (err error) {
+ _, err = db.Exec("ALTER TABLE `av_" + tableID + "` DROP COLUMN `" + column + "`")
+ if nil != err {
+ logging.LogErrorf("drop column [%s] failed: %s", column, err)
+ return
+ }
+ return
+}
+
+func addAttributeViewTableColumn(db *sql.DB, tableID string, column string) (err error) {
+ _, err = db.Exec("ALTER TABLE `av_" + tableID + "` ADD COLUMN `" + column + "`")
+ if nil != err {
+ logging.LogErrorf("add column [%s] failed: %s", column, err)
+ return
+ }
+ return
+}
+
+func dropAttributeViewTable(db *sql.DB, tableID string) (err error) {
+ _, err = db.Exec("DROP TABLE IF EXISTS `av_" + tableID + "`")
+ if nil != err {
+ logging.LogErrorf("drop table [%s] failed: %s", tableID, err)
+ return
+ }
+ return
+}
+
+func createAttributeViewTable(db *sql.DB, tableID string, column []string) (err error) {
+ _, err = db.Exec("CREATE TABLE IF NOT EXISTS `av_" + tableID + "` (id, " + strings.Join(column, ", ") + ")")
+ if nil != err {
+ logging.LogErrorf("create table [%s] failed: %s", tableID, err)
+ return
+ }
+ return
+}
diff --git a/kernel/av/column.go b/kernel/av/column.go
new file mode 100644
index 000000000..3eb5b47a1
--- /dev/null
+++ b/kernel/av/column.go
@@ -0,0 +1,49 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+// Column 描述了属性视图的列。
+type Column interface {
+
+ // ID 用于获取列 ID。
+ ID() string
+
+ // Name 用于获取列名。
+ Name() string
+
+ // Type 用于获取列类型。
+ Type() string
+}
+
+// BaseColumn 描述了属性视图的基础结构。
+type BaseColumn struct {
+ BaseID string `json:"id"` // 列 ID
+ BaseName string `json:"name"` // 列名
+ BaseType string `json:"type"` // 列类型
+}
+
+func (c *BaseColumn) ID() string {
+ return c.BaseID
+}
+
+func (c *BaseColumn) Name() string {
+ return c.BaseName
+}
+
+func (c *BaseColumn) Type() string {
+ return c.BaseType
+}
diff --git a/kernel/av/column_date.go b/kernel/av/column_date.go
new file mode 100644
index 000000000..7f71c3e18
--- /dev/null
+++ b/kernel/av/column_date.go
@@ -0,0 +1,21 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+type ColumnDate struct {
+ *BaseColumn
+}
diff --git a/kernel/av/column_number.go b/kernel/av/column_number.go
new file mode 100644
index 000000000..d1914af83
--- /dev/null
+++ b/kernel/av/column_number.go
@@ -0,0 +1,21 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+type ColumnNumber struct {
+ *BaseColumn
+}
diff --git a/kernel/av/column_relation.go b/kernel/av/column_relation.go
new file mode 100644
index 000000000..d7696c42d
--- /dev/null
+++ b/kernel/av/column_relation.go
@@ -0,0 +1,27 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+type ColumnRelation struct {
+ *BaseColumn
+ AttributeViewID string `json:"attributeViewId"` // 关联的属性视图 ID
+}
+
+type AttributeViewColumnRollup struct {
+ *BaseColumn
+ RelationID string `json:"relationId"` // 目标关联列 ID
+}
diff --git a/kernel/av/column_select.go b/kernel/av/column_select.go
new file mode 100644
index 000000000..db00ac56a
--- /dev/null
+++ b/kernel/av/column_select.go
@@ -0,0 +1,27 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+type ColumnSelect struct {
+ *BaseColumn
+ Options []*ColumnSelectOption `json:"options"`
+}
+
+type ColumnSelectOption struct {
+ Name string `json:"name"`
+ Color string `json:"color"`
+}
diff --git a/kernel/av/column_text.go b/kernel/av/column_text.go
new file mode 100644
index 000000000..047724c3e
--- /dev/null
+++ b/kernel/av/column_text.go
@@ -0,0 +1,21 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+type ColumnText struct {
+ *BaseColumn
+}
diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go
new file mode 100644
index 000000000..a15f55243
--- /dev/null
+++ b/kernel/model/attribute_view.go
@@ -0,0 +1,33 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package model
+
+import (
+ "github.com/88250/lute/ast"
+ "github.com/siyuan-note/siyuan/kernel/sql"
+)
+
+func CreateAttributeView() (err error) {
+ avID := ast.NewNodeID()
+ av := &sql.AttributeView{}
+
+ return
+}
+
+func addBlockToAttributeView(block *Block, avID string) {
+
+}
diff --git a/kernel/sql/attribute_view.go b/kernel/sql/attribute_view.go
deleted file mode 100644
index 6fd1acaa7..000000000
--- a/kernel/sql/attribute_view.go
+++ /dev/null
@@ -1,220 +0,0 @@
-// SiYuan - Build Your Eternal Digital Garden
-// Copyright (c) 2020-present, b3log.org
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package sql
-
-import (
- "path/filepath"
- "strings"
-
- "github.com/88250/gulu"
- "github.com/siyuan-note/filelock"
- "github.com/siyuan-note/logging"
- "github.com/siyuan-note/siyuan/kernel/util"
-)
-
-type AttributeView struct {
- Attributes []string // 属性列表,即表格的列名
- Blocks []string // 存储的块 ID 列表,即表格的行记录
-}
-
-// syncAttributeViewTableFromJSON 从 JSON 文件同步属性视图表,用于数据同步后将属性视图 JSON 文件同步到数据库。
-func syncAttributeViewTableFromJSON(tableID string) (err error) {
- avJSONPath := getAttributeViewJSONPath(tableID)
- data, err := filelock.ReadFile(avJSONPath)
- if nil != err {
- logging.LogErrorf("read attribute view table failed: %s", err)
- return
- }
-
- var attributeView AttributeView
- if err = gulu.JSON.UnmarshalJSON(data, &attributeView); nil != err {
- logging.LogErrorf("unmarshal attribute view table failed: %s", err)
- return
- }
-
- oldColumns, err := getAttributeViewTableColumns(tableID)
- if nil != err {
- return
- }
-
- // 删除多余的列
- for _, column := range oldColumns {
- if !gulu.Str.Contains(column, attributeView.Attributes) {
- if err = dropAttributeViewTableColumn(tableID, column); nil != err {
- return
- }
- }
- }
-
- // 添加缺失的列
- for _, column := range attributeView.Attributes {
- if !gulu.Str.Contains(column, oldColumns) {
- if err = addAttributeViewTableColumn(tableID, column); nil != err {
- return
- }
- }
- }
-
- // 删除多余的记录
- oldIDs, err := getAttributeViewRecordIDs(tableID)
- if nil != err {
- return
- }
- for _, id := range oldIDs {
- if !gulu.Str.Contains(id, attributeView.Blocks) {
- if err = deleteAttributeViewTableBlock(tableID, id); nil != err {
- return
- }
- }
- }
-
- // 添加缺失的记录
- for _, id := range attributeView.Blocks {
- if !gulu.Str.Contains(id, oldIDs) {
- if err = addAttributeViewTableBlock(tableID, id); nil != err {
- return
- }
- }
- }
-
- return
-}
-
-// syncAttributeViewTableToJSON 同步属性视图表到 JSON 文件,用于将数据库中的属性视图持久化到 JSON 文件中。
-func syncAttributeViewTableToJSON(tableID string) (err error) {
- columns, err := getAttributeViewTableColumns(tableID)
- if nil != err {
- return
- }
-
- ids, err := getAttributeViewRecordIDs(tableID)
- if nil != err {
- return
- }
-
- attributeView := &AttributeView{Attributes: columns, Blocks: ids}
- data, err := gulu.JSON.MarshalJSON(attributeView)
- if nil != err {
- logging.LogErrorf("marshal attribute view table failed: %s", err)
- return
- }
-
- avJSONPath := getAttributeViewJSONPath(tableID)
- if err = filelock.WriteFile(avJSONPath, data); nil != err {
- logging.LogErrorf("save attribute view table failed: %s", err)
- return
- }
- return
-}
-
-func getAttributeViewJSONPath(tableID string) string {
- return filepath.Join(util.DataDir, "storage", "av", tableID+".json")
-}
-
-func addAttributeViewTableBlock(tableID string, id string) (err error) {
- _, err = db.Exec("INSERT INTO `av_"+tableID+"` (id) VALUES (?)", id)
- if nil != err {
- logging.LogErrorf("add av table block [%s] failed: %s", id, err)
- return
- }
- return
-}
-
-func deleteAttributeViewTableBlock(tableID string, id string) (err error) {
- _, err = db.Exec("DELETE FROM `av_"+tableID+"` WHERE id = ?", id)
- if nil != err {
- logging.LogErrorf("delete av table block [%s] failed: %s", id, err)
- return
- }
- return
-}
-
-func getAttributeViewRecordIDs(tableID string) (ret []string, err error) {
- rows, err := db.Query("SELECT id FROM `av_" + tableID + "`")
- if nil != err {
- logging.LogErrorf("get record ids failed: %s", err)
- return
- }
- defer rows.Close()
-
- for rows.Next() {
- var id string
- err = rows.Scan(&id)
- if nil != err {
- logging.LogErrorf("get record ids failed: %s", err)
- return
- }
- ret = append(ret, id)
- }
- return
-}
-
-func getAttributeViewTableColumns(tableID string) (ret []string, err error) {
- rows, err := db.Query("SHOW COLUMNS FROM `av_" + tableID + "`")
- if nil != err {
- logging.LogErrorf("get columns failed: %s", err)
- return
- }
- defer rows.Close()
-
- for rows.Next() {
- var column string
- err = rows.Scan(&column)
- if nil != err {
- logging.LogErrorf("get columns failed: %s", err)
- return
- }
- ret = append(ret, column)
- }
- return
-}
-
-func dropAttributeViewTableColumn(tableID string, column string) (err error) {
- _, err = db.Exec("ALTER TABLE `av_" + tableID + "` DROP COLUMN `" + column + "`")
- if nil != err {
- logging.LogErrorf("drop column [%s] failed: %s", column, err)
- return
- }
- return
-}
-
-func addAttributeViewTableColumn(tableID string, column string) (err error) {
- _, err = db.Exec("ALTER TABLE `av_" + tableID + "` ADD COLUMN `" + column + "`")
- if nil != err {
- logging.LogErrorf("add column [%s] failed: %s", column, err)
- return
- }
- return
-}
-
-func dropAttributeViewTable(tableID string) (err error) {
- _, err = db.Exec("DROP TABLE IF EXISTS `av_" + tableID + "`")
- if nil != err {
- logging.LogErrorf("drop table [%s] failed: %s", tableID, err)
- return
- }
- return
-}
-
-func createAttributeViewTable(tableID string, column []string) (err error) {
- _, err = db.Exec("CREATE TABLE IF NOT EXISTS `av_" + tableID + "` (id, " + strings.Join(column, ", ") + ")")
- if nil != err {
- logging.LogErrorf("create table [%s] failed: %s", tableID, err)
- return
- }
- return
-}