diff --git a/res/gtk/icons/category-search-symbolic.svg b/res/gtk/icons/category-search-symbolic.svg
new file mode 100644
index 0000000..e4de46c
--- /dev/null
+++ b/res/gtk/icons/category-search-symbolic.svg
@@ -0,0 +1,4 @@
+
diff --git a/res/gtk/query-view.blp b/res/gtk/query-view.blp
index f2eab86..82b461b 100644
--- a/res/gtk/query-view.blp
+++ b/res/gtk/query-view.blp
@@ -14,7 +14,7 @@ template $PsequelQueryView : Adw.Bin {
Box {
orientation: vertical;
- width-request: 280;
+ width-request: 300;
Box {
margin-start: 8;
@@ -40,58 +40,137 @@ template $PsequelQueryView : Adw.Bin {
Separator {}
- Box {
+ Adw.ViewSwitcher tables_views_switcher {
+
+ styles ["big-icon"]
+ height-request: 40;
margin-start: 8;
margin-end: 8;
- margin-top: 4;
- margin-bottom: 8;
-
- Label {
- styles ["title-3"]
- margin-top: 8;
- margin-start: 4;
- halign: start;
- margin-bottom: 0;
- label: "Tables";
- }
-
- Separator {
- hexpand: true;
- styles ["spacer"]
- }
-
- ToggleButton search_btn {
- styles ["flat"]
- tooltip-text: "Search tables";
- icon-name: "loupe-large-symbolic";
-
- toggled => $on_show_search();
- }
-
+ margin-top: 8;
+ // margin-bottom: 8;
+ policy: wide;
+ stack: sql_views;
}
-
-
- Revealer {
- transition-duration: 300;
- reveal-child: bind search_btn.active;
- styles ["background"]
- SearchEntry search_entry {
- placeholder-text: "Search Tables";
- margin-start: 4;
- margin-bottom: 4;
+ Adw.ViewStack sql_views {
+ hexpand: true;
+ vexpand: true;
+ Adw.ViewStackPage {
+ icon-name: "table-symbolic";
+
+ child: Box {
+ orientation: vertical;
+ Box {
+ margin-start: 8;
+ margin-end: 8;
+ margin-top: 4;
+ margin-bottom: 8;
+
+ Label {
+ styles ["title-3"]
+ margin-top: 8;
+ margin-start: 4;
+ halign: start;
+ margin-bottom: 0;
+ label: "Tables";
+ }
+
+ Separator {
+ hexpand: true;
+ styles ["spacer"]
+ }
+
+ ToggleButton search_btn {
+ styles ["flat"]
+ tooltip-text: "Search tables";
+ icon-name: "loupe-large-symbolic";
+
+ toggled => $on_show_search();
+ }
+ }
- search-changed => $on_search();
+ Revealer {
+ transition-duration: 300;
+ reveal-child: bind search_btn.active;
+ styles ["background"]
+ SearchEntry search_table_entry {
+ placeholder-text: "Search Tables";
+ margin-start: 4;
+ margin-bottom: 4;
+
+ search-changed => $on_search();
+ }
+
+ }
+
+ ScrolledWindow {
+ vexpand: true;
+ ListBox table_list {
+ styles ["navigation-sidebar"]
+ selected-rows-changed => $table_selected();
+ }
+
+ }
+ };
}
-
- }
-
- ScrolledWindow {
- vexpand: true;
- ListBox table_list {
- styles ["navigation-sidebar"]
+
+ Adw.ViewStackPage {
+ icon-name: "category-search-symbolic";
+
+ child: Box {
+ orientation: vertical;
+ Box {
+ margin-start: 8;
+ margin-end: 8;
+ margin-top: 4;
+ margin-bottom: 8;
+
+ Label {
+ styles ["title-3"]
+ margin-top: 8;
+ margin-start: 4;
+ halign: start;
+ margin-bottom: 0;
+ label: "Views";
+ }
+
+ Separator {
+ hexpand: true;
+ styles ["spacer"]
+ }
+
+ ToggleButton search_views_btn {
+ styles ["flat"]
+ tooltip-text: "Search tables";
+ icon-name: "loupe-large-symbolic";
+
+ toggled => $on_show_view_search();
+ }
+ }
+
+ Revealer {
+ transition-duration: 300;
+ reveal-child: bind search_views_btn.active;
+ styles ["background"]
+ SearchEntry search_views_entry {
+ placeholder-text: "Search Views";
+ margin-start: 4;
+ margin-bottom: 4;
+
+ search-changed => $on_view_search();
+ }
+
+ }
+
+ ScrolledWindow {
+ vexpand: true;
+ ListBox views_list {
+ styles ["navigation-sidebar"]
+ selected-rows-changed => $view_selected();
+ }
+ }
+ };
}
-
}
Box {
diff --git a/res/gtk/style.css b/res/gtk/style.css
index 69acb06..49d9fce 100644
--- a/res/gtk/style.css
+++ b/res/gtk/style.css
@@ -1,3 +1,7 @@
.text-bold {
font-size: 105%;
+}
+
+.big-icon image {
+ -gtk-icon-size: 20px;
}
\ No newline at end of file
diff --git a/res/gtk/table-data.blp b/res/gtk/table-data.blp
new file mode 100644
index 0000000..e69de29
diff --git a/res/gtk/table-structure.blp b/res/gtk/table-structure.blp
index b3b1d58..896f8d0 100644
--- a/res/gtk/table-structure.blp
+++ b/res/gtk/table-structure.blp
@@ -9,7 +9,6 @@ template $PsequelTableStructure : Gtk.Box {
margin-top: 12;
margin-bottom: 12;
- styles ["frame"]
// width-request: 960;
// height-request: 800;
@@ -19,12 +18,12 @@ template $PsequelTableStructure : Gtk.Box {
label: "Columns";
halign: start;
}
- ColumnView columns {
-
- show-column-separators: true;
- show-row-separators: true;
- vexpand: true;
- styles ["frame"]
+ ScrolledWindow {
+ ColumnView columns {
+ show-column-separators: true;
+ show-row-separators: true;
+ vexpand: true;
+ }
}
Label {
@@ -32,12 +31,14 @@ template $PsequelTableStructure : Gtk.Box {
label: "Indexes";
halign: start;
}
- ColumnView indexes {
+
+ ScrolledWindow {
+ ColumnView indexes {
- show-column-separators: true;
- show-row-separators: true;
- styles ["frame"]
- vexpand: true;
+ show-column-separators: true;
+ show-row-separators: true;
+ vexpand: true;
+ }
}
Label {
@@ -45,11 +46,13 @@ template $PsequelTableStructure : Gtk.Box {
label: "Foreign Keys";
halign: start;
}
- ColumnView foreign_key {
- show-column-separators: true;
- show-row-separators: true;
- styles ["frame"]
- vexpand: true;
+ ScrolledWindow {
+ ColumnView foreign_key {
+
+ show-column-separators: true;
+ show-row-separators: true;
+ vexpand: true;
+ }
}
}
diff --git a/res/gtk/welcome.blp b/res/gtk/welcome.blp
index c25c248..c1b02de 100644
--- a/res/gtk/welcome.blp
+++ b/res/gtk/welcome.blp
@@ -14,7 +14,7 @@ template $PsequelConnectionView : Adw.Bin {
[start]
$PsequelConnectionSidebar sidebar {
- width-request: 280;
+ width-request: 300;
form: form;
}
diff --git a/res/psequel.gresource.xml b/res/psequel.gresource.xml
index fbf43ef..55ba3bd 100644
--- a/res/psequel.gresource.xml
+++ b/res/psequel.gresource.xml
@@ -22,5 +22,6 @@
gtk/icons/columns-symbolic.svg
gtk/icons/step-out-symbolic.svg
gtk/icons/arrow-into-box-symbolic.svg
+ gtk/icons/category-search-symbolic.svg
diff --git a/src/application.vala b/src/application.vala
index 395b787..2d12dde 100644
--- a/src/application.vala
+++ b/src/application.vala
@@ -59,7 +59,7 @@ namespace Psequel {
}
query_service = new QueryService (background);
- table_list = new ObservableArrayList
();
+ table_list = new ObservableArrayList ();
signals = new AppSignals ();
load_user_data ();
diff --git a/src/models/table.vala b/src/models/table.vala
index bc87f94..654476e 100644
--- a/src/models/table.vala
+++ b/src/models/table.vala
@@ -2,43 +2,62 @@ using Postgres;
using Gee;
namespace Psequel {
- public class Table : Object {
- // Keep references to result so data is not destroy.
- private Result result;
+ public delegate Relation.Row TransFormsFunc (Relation.Row row);
+
+ public class Relation : Object {
+
public int rows { get; private set; }
- private int cols { get; private set; }
+ public int cols { get; private set; }
- private Header header { get; private set; }
- public ArrayList data;
+ private ArrayList data;
+ private ArrayList headers;
- public Table (owned Result res) {
+ public Relation (owned Result res) {
Object ();
- result = (owned) res;
- load_data ();
+ load_data ((owned) res);
}
- private void load_data () {
+ private Relation.from_data (ArrayList headers, ArrayList data) {
+ this.headers = headers;
+ this.data = data;
+ this.rows = data.size;
+ this.cols = headers.size;
+ }
+
+ private void load_data (owned Result result) {
assert_nonnull (result);
rows = result.get_n_tuples ();
cols = result.get_n_fields ();
- header = new Header ();
+ this.headers = new ArrayList ();
for (int i = 0; i < cols; i++) {
- header.add_field (result.get_field_name (i));
+ headers.add (result.get_field_name (i));
}
this.data = new ArrayList ();
for (int i = 0; i < rows; i++) {
- data.add (new Row (header));
+ data.add (new Row ());
for (int j = 0; j < cols; j++) {
data[i].add_field (result.get_value (i, j));
}
}
}
+ public Relation transform (ArrayList new_headers, TransFormsFunc func) {
+
+ var new_rows = new ArrayList ();
+
+ assert_nonnull (this.data);
+ foreach (var row in this.data) {
+ new_rows.add (func (row));
+ }
+
+ return new Relation.from_data (new_headers, new_rows);
+ }
+
public string to_string () {
return @"Table ($rows x $cols)";
}
@@ -53,30 +72,39 @@ namespace Psequel {
return data.get (index);
}
+
/**
* Helper class for ease of use with Table. DO NOT use it outside of Table class.
*/
public class Row : Object {
- private ArrayList data;
- private Header header;
+ private ArrayList data;
public int size {
get { return data.size; }
}
- internal Row (Header header) {
- this.data = new ArrayList ();
- this.header = header;
+ internal Row () {
+ this.data = new ArrayList ();
}
public void add_field (string item) {
- assert (data.size < header.size);
data.add (item);
}
- public new unowned string @get (int index) {
+ public void insert_field (int index, string item) {
+ data.insert (index, item);
+ }
+
+ public void remove_at (int index) {
+ assert (index < size);
+ assert (index >= 0);
+
+ data.remove_at (index);
+ }
+
+ public new string @get (int index) {
return data.get (index);
}
@@ -90,33 +118,5 @@ namespace Psequel {
return builder.free_and_steal ();
}
}
-
- /**
- * Helper class for ease of use with Table. DO NOT use it outside of Table class.
- */
- public class Header : Object {
- private ArrayList data;
-
- public int size {
- get { return data.size; }
- }
-
- internal Header () {
- this.data = new ArrayList ();
- }
-
- public void add_field (string item) {
- data.add (item);
- }
-
- public new unowned string @get (int index) {
- return data.get (index);
- }
-
- public string to_string () {
- var row_data = data.fold ((seed, item) => seed + item.dup () + ",", "");
- return row_data;
- }
- }
}
}
\ No newline at end of file
diff --git a/src/services/query_service.vala b/src/services/query_service.vala
index bd787d6..febefc8 100644
--- a/src/services/query_service.vala
+++ b/src/services/query_service.vala
@@ -15,7 +15,7 @@ namespace Psequel {
this.background = background;
}
- public async Table db_schemas () throws PsequelError {
+ public async Relation db_schemas () throws PsequelError {
var stmt = "select schema_name from information_schema.schemata;";
var res = yield exec_query (stmt);
@@ -23,7 +23,58 @@ namespace Psequel {
return res;
}
- public async Table db_table_info (string schema, string table_name) throws PsequelError {
+ public async Relation db_table_fk (string schema, string table_name) throws PsequelError {
+ string stmt = """
+ SELECT conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
+ FROM pg_catalog.pg_constraint r
+ WHERE r.conrelid = $1::regclass AND r.contype = 'f';
+ """;
+
+ var params = new ArrayList ();
+ params.add (new Variant.string (@"$schema.$table_name"));
+
+ var headers = new ArrayList ();
+ headers.add_all_array ({
+ "Key Name",
+ "Columns",
+ "Foreign Table",
+ "Foreign Columns",
+ "On Update",
+ "On Delete",
+ });
+
+ var raw = yield exec_query_params (stmt, params);
+ var result = raw.transform (headers, (old_row) => {
+ var new_row = new Relation.Row ();
+ new_row.add_field (old_row[0]);
+
+ var fk_def = old_row[1];
+
+ // Match the index type and column from fk_def
+ var regex = /FOREIGN KEY \(([$a-zA-Z_, ]+)\) REFERENCES ([a-zA-Z_, ]+)\(([a-zA-Z_, ]+)\)( ON UPDATE (CASCADE))?( ON DELETE (RESTRICT))?/;
+ MatchInfo match_info;
+ if (regex.match (fk_def, 0, out match_info)) {
+
+ new_row.add_field (match_info.fetch (1));
+ new_row.add_field (match_info.fetch (2));
+ new_row.add_field (match_info.fetch (3));
+ new_row.add_field (match_info.fetch (5) == "" ? "NO ACTION" : match_info.fetch (5));
+ new_row.add_field (match_info.fetch (7) == "" ? "NO ACTION" : match_info.fetch (7));
+ } else {
+ debug ("Regex not match: %s", fk_def);
+ assert_not_reached ();
+ }
+ debug ("%s", new_row.to_string ());
+
+ return new_row;
+ });
+
+
+ return result;
+
+ }
+
+ public async Relation db_table_info (string schema, string table_name) throws PsequelError {
string stmt = """
SELECT column_name AS "Column Name",
data_type AS "Type",
@@ -43,7 +94,57 @@ namespace Psequel {
return yield exec_query_params (stmt, params);
}
- public async Table db_tablenames (string schema = "public") throws PsequelError {
+ public async Relation db_table_indexes (string schema, string table_name) throws PsequelError {
+ string stmt = """
+ SELECT indexname, indexdef FROM pg_indexes
+ WHERE schemaname = $1
+ AND tablename = $2;
+ """;
+
+ var params = new ArrayList ();
+ params.add (new Variant.string (schema));
+ params.add (new Variant.string (table_name));
+
+ var headers = new ArrayList ();
+ headers.add_all_array ({
+ "Index Name",
+ "Unique",
+ "Type",
+ "Columns",
+ });
+
+ var raw_result = yield exec_query_params (stmt, params);
+
+ var result = raw_result.transform (headers, (old_row) => {
+ var new_row = new Relation.Row ();
+ new_row.add_field (old_row[0]);
+
+ var indexdef = old_row[1];
+ if (indexdef.contains ("UNIQUE")) {
+ new_row.add_field ("YES");
+ } else {
+ new_row.add_field ("NO");
+ }
+
+ // Match the index type and column from indexdef, group 1 is type, group 2 is the column list.
+ var regex = /USING (btree|hash|gist|spgist|gin|brin|[\w]+) \(([a-zA-Z1-9+\-*\/_, ()]+)\)/;
+ MatchInfo match_info;
+ if (regex.match (indexdef, 0, out match_info)) {
+
+ new_row.add_field (match_info.fetch (1));
+ new_row.add_field (match_info.fetch (2));
+ } else {
+ debug ("Regex not match: %s", indexdef);
+ assert_not_reached ();
+ }
+
+ return new_row;
+ });
+
+ return result;
+ }
+
+ public async Relation db_tablenames (string schema = "public") throws PsequelError {
var builder = new StringBuilder ("select tablename from pg_tables where schemaname=");
builder.append (@"\'$schema\';");
@@ -51,7 +152,22 @@ namespace Psequel {
string stmt = builder.free_and_steal ();
var res = yield exec_query_internal (stmt);
- var table = new Table ((owned) res);
+ var table = new Relation ((owned) res);
+
+ return table;
+ }
+
+ public async Relation db_views (string schema = "public") throws PsequelError {
+
+ string stmt = """
+ select table_name from INFORMATION_SCHEMA.views WHERE table_schema = $1;
+ """;
+ var params = new ArrayList ();
+ params.add (new Variant.string (schema));
+
+ var res = yield exec_query_params_internal (stmt, params);
+
+ var table = new Relation ((owned) res);
return table;
}
@@ -93,24 +209,24 @@ namespace Psequel {
}
}
- public async Table exec_query (string query) throws PsequelError {
+ public async Relation exec_query (string query) throws PsequelError {
var result = yield exec_query_internal (query);
// check query status
check_query_status (result);
- var table = new Table ((owned) result);
+ var table = new Relation ((owned) result);
return table;
}
- public async Table exec_query_params (string query, ArrayList params) throws PsequelError {
+ public async Relation exec_query_params (string query, ArrayList params) throws PsequelError {
var result = yield exec_query_params_internal (query, params);
// check query status
check_query_status (result);
- var table = new Table ((owned) result);
+ var table = new Relation ((owned) result);
return table;
}
diff --git a/src/services/resource_manager.vala b/src/services/resource_manager.vala
index 6788f13..1d2086c 100644
--- a/src/services/resource_manager.vala
+++ b/src/services/resource_manager.vala
@@ -31,7 +31,7 @@ namespace Psequel {
/**
* List of table in current schema.
*/
- public ObservableArrayList table_list;
+ public ObservableArrayList table_list;
/**
* Application
diff --git a/src/services/signal.vala b/src/services/signal.vala
index a9987b7..f6f7a92 100644
--- a/src/services/signal.vala
+++ b/src/services/signal.vala
@@ -9,8 +9,11 @@ namespace Psequel {
* Emit when the table list changed.
*/
public signal void table_list_changed ();
+ public signal void views_list_changed ();
+ public signal void table_selected_changed (string schema, string table);
+
public signal void database_connected ();
/**
* Should only init onces by the resource manager. Should be single on but i'm lazy
diff --git a/src/ui/query_view.vala b/src/ui/query_view.vala
index e008b50..eea0a7e 100644
--- a/src/ui/query_view.vala
+++ b/src/ui/query_view.vala
@@ -9,9 +9,12 @@ namespace Psequel {
private unowned AppSignals signals;
- private ObservableArrayList table_names;
+ private ObservableArrayList table_names;
+ private ObservableArrayList views_names;
+
private Gtk.FilterListModel tablelist_model;
+ private Gtk.FilterListModel viewslist_model;
private Gtk.StringList schema_model;
public QueryView () {
@@ -21,9 +24,11 @@ namespace Psequel {
construct {
query_service = ResourceManager.instance ().query_service;
signals = ResourceManager.instance ().signals;
+ // tables_views_switcher.get_
set_up_table_list ();
set_up_schema ();
+ set_up_views_list ();
connect_signals ();
}
@@ -47,10 +52,28 @@ namespace Psequel {
tablelist_model.get_filter ().changed (Gtk.FilterChange.DIFFERENT);
}
+ [GtkCallback]
+ private void on_view_search (Gtk.SearchEntry entry) {
+ debug ("Search views: %s", entry.text);
+ viewslist_model.get_filter ().changed (Gtk.FilterChange.DIFFERENT);
+ }
+
+ [GtkCallback]
+ private void view_selected () {
+ debug ("view selected.");
+ }
+
[GtkCallback]
private void on_show_search (Gtk.ToggleButton btn) {
if (btn.active) {
- search_entry.grab_focus ();
+ search_table_entry.grab_focus ();
+ }
+ }
+
+ [GtkCallback]
+ private void on_show_view_search (Gtk.ToggleButton btn) {
+ if (btn.active) {
+ search_views_entry.grab_focus ();
}
}
@@ -59,21 +82,52 @@ namespace Psequel {
*/
private void schema_changed () {
signals.table_list_changed ();
+ signals.views_list_changed ();
}
+
+ [GtkCallback]
+ private void table_selected () {
+ var row = table_list.get_selected_row ();
+
+ if (row == null) {
+ signals.table_selected_changed ("", "");
+ } else {
+ var cur_schema = schema.get_selected_item () as Gtk.StringObject;
+ assert_nonnull (cur_schema);
+
+ var label = row.child.get_last_child () as Gtk.Label;
+
+ debug ("Emit table_selected_changed");
+ signals.table_selected_changed (cur_schema.string, label.get_label ());
+ }
+ }
/**
* Filter table name base on seach entry.
*/
private bool search_filter_func (Object item) {
- assert (item is Table.Row);
+ assert (item is Relation.Row);
- var row = item as Table.Row;
+ var row = item as Relation.Row;
var table_name = row.get (0);
- var search_text = search_entry.text;
+ var search_text = search_table_entry.text;
return table_name.contains (search_text);
}
+ /**
+ * Filter table name base on seach entry.
+ */
+ private bool view_filter_func (Object item) {
+ assert (item is Relation.Row);
+
+ var row = item as Relation.Row;
+ var view_name = row.get (0);
+ var search_text = search_views_entry.text;
+
+ return view_name.contains (search_text);
+ }
+
/**
* Reload schema list to the drop down by fetching database.
*/
@@ -104,6 +158,7 @@ namespace Psequel {
var relations = yield query_service.db_tablenames (cur_schema);
+ table_list.unselect_all ();
table_names.clear ();
foreach (var item in relations) {
table_names.add (item);
@@ -112,10 +167,25 @@ namespace Psequel {
debug ("Tables list reloaded, got %d tables in schema %s", table_names.size, cur_schema);
}
+ private async void reload_views () throws PsequelError {
+
+ var cur_schema = ((Gtk.StringObject) schema.selected_item).string ?? "public";
+
+ var relations = yield query_service.db_views (cur_schema);
+
+ views_list.unselect_all ();
+ views_names.clear ();
+ foreach (var item in relations) {
+ views_names.add (item);
+ }
+
+ debug ("Views list reloaded, got %d views in schema %s", table_names.size, cur_schema);
+ }
+
/** Create row widget from query result.
*/
private Gtk.ListBoxRow table_row_factory (Object obj) {
- var row_data = obj as Table.Row;
+ var row_data = obj as Relation.Row;
var row = new Gtk.ListBoxRow ();
@@ -132,14 +202,40 @@ namespace Psequel {
return row;
}
+ private Gtk.ListBoxRow view_row_factory (Object obj) {
+ var row_data = obj as Relation.Row;
+
+ var row = new Gtk.ListBoxRow ();
+
+ var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 8);
+ var icon = new Gtk.Image ();
+ icon.icon_name = "category-search-symbolic";
+ var label = new Gtk.Label (row_data[0]);
+
+ box.append (icon);
+ box.append (label);
+
+ row.child = box;
+
+ return row;
+ }
+
private void set_up_table_list () {
- this.table_names = new ObservableArrayList ();
+ this.table_names = new ObservableArrayList ();
var filter = new Gtk.CustomFilter (search_filter_func);
this.tablelist_model = new Gtk.FilterListModel (table_names, filter);
this.table_list.bind_model (tablelist_model, table_row_factory);
}
+ private void set_up_views_list () {
+ this.views_names = new ObservableArrayList ();
+
+ var filter = new Gtk.CustomFilter (view_filter_func);
+ this.viewslist_model = new Gtk.FilterListModel (views_names, filter);
+ this.views_list.bind_model (viewslist_model, view_row_factory);
+ }
+
private void set_up_schema () {
this.schema_model = new Gtk.StringList (null);
this.schema.set_model (schema_model);
@@ -151,6 +247,11 @@ namespace Psequel {
reload_tables.begin ();
});
+ signals.views_list_changed.connect (() => {
+ debug ("Handle views_list_changed.");
+ reload_views.begin ();
+ });
+
signals.database_connected.connect (() => {
debug ("Handle database_connected.");
reload_schema.begin ();
@@ -163,7 +264,13 @@ namespace Psequel {
private unowned Gtk.ListBox table_list;
[GtkChild]
- private unowned Gtk.SearchEntry search_entry;
+ private unowned Gtk.SearchEntry search_table_entry;
+
+ [GtkChild]
+ private unowned Gtk.ListBox views_list;
+
+ [GtkChild]
+ private unowned Gtk.SearchEntry search_views_entry;
[GtkChild]
private unowned Gtk.DropDown schema;
diff --git a/src/ui/table_structure.vala b/src/ui/table_structure.vala
index 81ae23a..0c0aae2 100644
--- a/src/ui/table_structure.vala
+++ b/src/ui/table_structure.vala
@@ -6,6 +6,12 @@ namespace Psequel {
private AppSignals signals;
private QueryService query_service;
+ // private Table table;
+ private ObservableArrayList columns_model;
+ private ObservableArrayList index_model;
+ private ObservableArrayList fk_model;
+ // private Gtk.SingleSelection selection_model;
+
public TableStructure () {
Object ();
}
@@ -15,17 +21,133 @@ namespace Psequel {
query_service = ResourceManager.instance ().query_service;
signals = ResourceManager.instance ().signals;
- signals.database_connected.connect (() => {
- query_service.db_table_info.begin ("public", "city", (object, res) => {
- Table table = query_service.db_table_info.end (res);
+ columns_model = new ObservableArrayList ();
+ index_model = new ObservableArrayList ();
+ fk_model = new ObservableArrayList ();
- debug (table.to_string ());
- foreach (var item in table) {
- debug (item.to_string ());
- }
- });
+
+ signals.table_selected_changed.connect ((schema, tbname) => {
+ debug ("%s, %s", schema, tbname);
+ reload_table_columns.begin (schema, tbname);
+ reload_table_indexes.begin (schema, tbname);
+ reload_table_fk.begin (schema, tbname);
});
+ var columns_title = new string[] {
+ "Column Name",
+ "Type",
+ "Length",
+ "Nullable",
+ "Default Value"
+ };
+
+ var index_titles = new string[] {
+ "Index Name",
+ "Unique",
+ "Type",
+ "Columns",
+ };
+
+ var fk_titles = new string[] {
+ "Key Name",
+ "Columns",
+ "Foreign Table",
+ "Foreign Columns",
+ "On Update",
+ "On Delete",
+ };
+
+ set_up_view (columns_title, columns_model, columns);
+ set_up_view (index_titles, index_model, indexes);
+ set_up_view (fk_titles, fk_model, foreign_key);
+ }
+
+ void set_up_view (string[] titles, ListModel model, Gtk.ColumnView view) {
+ for (int i = 0; i < titles.length; i++) {
+ var factory = new Gtk.SignalListItemFactory ();
+ factory.set_data ("index", i);
+
+ factory.setup.connect ((_fact, _item) => {
+ var label = new Gtk.Label (null);
+ label.halign = Gtk.Align.START;
+ label.margin_start = 8;
+ _item.child = label;
+ });
+
+ factory.bind.connect ((_fact, _item) => {
+ var row = _item.item as Relation.Row;
+ var label = _item.child as Gtk.Label;
+ int index = _fact.get_data ("index");
+ label.label = row[index];
+ });
+
+ Gtk.ColumnViewColumn column = new Gtk.ColumnViewColumn (titles[i], factory);
+ column.set_expand (true);
+ view.append_column (column);
+ }
+
+ var selection_model = new Gtk.SingleSelection (model);
+ view.set_model (selection_model);
+ }
+
+ private async void reload_table_columns (string schema, string tbname) {
+ try {
+ var relation = yield query_service.db_table_info (schema, tbname);
+
+ if (relation.cols != columns.columns.get_n_items ()) {
+ debug ("Programming Error: Query result cols != view columns");
+ assert_not_reached ();
+ }
+
+ columns_model.clear ();
+ foreach (var row in relation) {
+ columns_model.add (row);
+ }
+
+ } catch (PsequelError err) {
+ debug (err.message);
+ // ResourceManager.instance ().app
+ }
+ }
+
+ private async void reload_table_indexes (string schema, string tbname) {
+ try {
+ var relation = yield query_service.db_table_indexes (schema, tbname);
+
+ if (relation.cols != indexes.columns.get_n_items ()) {
+ debug ("Programming Error: Query result cols != view columns");
+ assert_not_reached ();
+ }
+
+ index_model.clear ();
+ foreach (var row in relation) {
+ index_model.add (row);
+ }
+
+ } catch (PsequelError err) {
+ debug (err.message);
+ // ResourceManager.instance ().app
+ }
+ }
+
+ private async void reload_table_fk (string schema, string tbname) {
+ try {
+ var relation = yield query_service.db_table_fk (schema, tbname);
+
+ if (relation.cols != foreign_key.columns.get_n_items ()) {
+ debug ("Programming Error: Query result cols != view columns");
+ assert_not_reached ();
+ }
+
+ fk_model.clear ();
+ foreach (var row in relation) {
+ fk_model.add (row);
+ }
+
+ } catch (PsequelError err) {
+ debug (err.message);
+ // ResourceManager.instance ().app
+ }
}
[GtkChild]
@@ -35,4 +157,5 @@ namespace Psequel {
[GtkChild]
private unowned Gtk.ColumnView foreign_key;
}
+
}
\ No newline at end of file