diff --git a/web/config.py b/web/config.py
index 6a3128e0a..e40201dcc 100644
--- a/web/config.py
+++ b/web/config.py
@@ -321,6 +321,12 @@ THREADED_MODE = True
##########################################################################
SQLALCHEMY_TRACK_MODIFICATIONS = False
+##########################################################################
+# Number of records to fetch in one batch in query tool when query result
+# set is large.
+##########################################################################
+ON_DEMAND_RECORD_COUNT = 1000
+
##########################################################################
# Local config settings
##########################################################################
diff --git a/web/pgadmin/feature_tests/connect_to_server_feature_test.py b/web/pgadmin/feature_tests/connect_to_server_feature_test.py
index e8de161a8..28a6df66f 100644
--- a/web/pgadmin/feature_tests/connect_to_server_feature_test.py
+++ b/web/pgadmin/feature_tests/connect_to_server_feature_test.py
@@ -7,6 +7,7 @@
#
##########################################################################
+import time
from selenium.webdriver import ActionChains
import config as app_config
@@ -53,6 +54,7 @@ class ConnectsToServerFeatureTest(BaseFeatureTest):
def _connects_to_server(self):
self.page.find_by_xpath("//*[@class='aciTreeText' and .='Servers']").click()
+ time.sleep(2)
self.page.driver.find_element_by_link_text("Object").click()
ActionChains(self.page.driver) \
.move_to_element(self.page.driver.find_element_by_link_text("Create")) \
@@ -72,6 +74,8 @@ class ConnectsToServerFeatureTest(BaseFeatureTest):
self.page.toggle_open_server(self.server['name'])
self.page.toggle_open_tree_item('Databases')
self.page.toggle_open_tree_item('acceptance_test_db')
+ # wait until all database dependant modules/js are loaded.
+ time.sleep(5)
self.page.toggle_open_tree_item('Schemas')
self.page.toggle_open_tree_item('public')
self.page.toggle_open_tree_item('Tables')
diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
index 848387689..26c787b68 100644
--- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py
+++ b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
@@ -6,6 +6,7 @@
# This software is released under the PostgreSQL Licence
#
##########################################################################
+import time
from selenium.webdriver import ActionChains
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
@@ -56,6 +57,7 @@ class PGDataypeFeatureTest(BaseFeatureTest):
self.page.find_by_xpath(
"//*[@class='aciTreeText' and .='Servers']"
).click()
+ time.sleep(2)
self.page.driver.find_element_by_link_text("Object").click()
ActionChains(self.page.driver) \
.move_to_element(
@@ -106,45 +108,19 @@ class PGDataypeFeatureTest(BaseFeatureTest):
self.page.fill_codemirror_area_with(query)
self.page.find_by_id("btn-flash").click()
wait = WebDriverWait(self.page.driver, 5)
- wait.until(EC.presence_of_element_located(
- (By.XPATH, "//*[@id='0']//*[@id='datagrid']/div[5]/div/div[1]/"
- "div[2]/span")))
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
# For every sample data-type value, check the expected output.
cnt = 2
- for val in expected_output[:10]:
+ cells = canvas.find_elements_by_css_selector('.slick-cell')
+ # remove first element as it is row number.
+ cells.pop(0)
+ for val, cell in zip(expected_output, cells):
try:
- source_code = self.page.find_by_xpath(
- "//*[@id='0']//*[@id='datagrid']/div[5]/div/div[1]/div["
- + str(cnt)
- + "]/span"
- ).get_attribute('innerHTML')
-
- PGDataypeFeatureTest.check_result(
- source_code,
- expected_output[cnt - 2]
- )
- cnt += 1
- except TimeoutException:
- assert False, "{0} does not match with {1}".format(
- val, expected_output[cnt]
- )
-
- cnt = 12
- for val in expected_output[10:]:
- try:
- if cnt == 14:
- xpath = "//*[@id='0']//*[@id='datagrid']/div[5]/div/div[1]/div[" \
- + str(cnt) \
- + "]/span"
- else:
- xpath = "//*[@id='0']//*[@id='datagrid']/div[5]/div/div/div[" \
- + str(cnt) \
- + "]"
-
- source_code = self.page.find_by_xpath(
- xpath
- ).get_attribute('innerHTML')
+ source_code = cell.get_attribute('innerHTML')
PGDataypeFeatureTest.check_result(
source_code,
diff --git a/web/pgadmin/feature_tests/query_tool_tests.py b/web/pgadmin/feature_tests/query_tool_tests.py
new file mode 100644
index 000000000..c05828757
--- /dev/null
+++ b/web/pgadmin/feature_tests/query_tool_tests.py
@@ -0,0 +1,734 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from __future__ import print_function
+import time
+import sys
+import config
+from selenium.webdriver import ActionChains
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.common.by import By
+from regression.python_test_utils import test_utils
+from regression.feature_utils.base_feature_test import BaseFeatureTest
+
+
+class QueryToolFeatureTest(BaseFeatureTest):
+ """
+ This feature test will test the different query tool features.
+ """
+
+ scenarios = [
+ ("Query tool feature test", dict())
+ ]
+
+ def before(self):
+ connection = test_utils.get_db_connection(self.server['db'],
+ self.server['username'],
+ self.server['db_password'],
+ self.server['host'],
+ self.server['port'])
+ test_utils.drop_database(connection, "acceptance_test_db")
+ test_utils.create_database(self.server, "acceptance_test_db")
+ self.page.wait_for_spinner_to_disappear()
+ self._connects_to_server()
+ self._locate_database_tree_node()
+ self.page.open_query_tool()
+
+ def runTest(self):
+ # on demand result set on scrolling.
+ print("\nOn demand result set on scrolling... ",
+ file=sys.stderr, end="")
+ self._on_demand_result()
+ print("OK.",
+ file=sys.stderr)
+ self._clear_query_tool()
+
+ # on demand result set on grid select all.
+ print("On demand result set on grid select all... ",
+ file=sys.stderr, end="")
+ self._on_demand_result_select_all_grid()
+ print("OK.",
+ file=sys.stderr)
+ self._clear_query_tool()
+
+ # on demand result set on column select all.
+ print("On demand result set on column select all... ",
+ file=sys.stderr, end="")
+ self._on_demand_result_select_all_column()
+ print("OK.",
+ file=sys.stderr)
+ self._clear_query_tool()
+
+ # explain query
+ print("Explain query... ", file=sys.stderr, end="")
+ self._query_tool_explain()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # explain query with verbose
+ print("Explain query with verbose... ", file=sys.stderr, end="")
+ self._query_tool_explain_verbose()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # explain query with costs
+ print("Explain query with costs... ", file=sys.stderr, end="")
+ self._query_tool_explain_cost()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # explain analyze query
+ print("Explain analyze query... ", file=sys.stderr, end="")
+ self._query_tool_explain_analyze()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # explain analyze query with buffers
+ print("Explain analyze query with buffers... ", file=sys.stderr, end="")
+ self._query_tool_explain_analyze_buffers()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # explain analyze query with timing
+ print("Explain analyze query with timing... ", file=sys.stderr, end="")
+ self._query_tool_explain_analyze_timing()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # auto commit disabled.
+ print("Auto commit disabled... ", file=sys.stderr, end="")
+ self._query_tool_auto_commit_disabled()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # auto commit enabled.
+ print("Auto commit enabled... ", file=sys.stderr, end="")
+ self._query_tool_auto_commit_enabled()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # auto rollback enabled.
+ print("Auto rollback enabled...", file=sys.stderr, end="")
+ self._query_tool_auto_rollback_enabled()
+ print(" OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ # cancel query.
+ print("Cancel query... ", file=sys.stderr, end="")
+ self._query_tool_cancel_query()
+ print("OK.", file=sys.stderr)
+ self._clear_query_tool()
+
+ def after(self):
+ self.page.remove_server(self.server)
+ connection = test_utils.get_db_connection(self.server['db'],
+ self.server['username'],
+ self.server['db_password'],
+ self.server['host'],
+ self.server['port'])
+ test_utils.drop_database(connection, "acceptance_test_db")
+
+ def _connects_to_server(self):
+ self.page.find_by_xpath(
+ "//*[@class='aciTreeText' and .='Servers']").click()
+ time.sleep(2)
+ self.page.driver.find_element_by_link_text("Object").click()
+ ActionChains(self.page.driver) \
+ .move_to_element(
+ self.page.driver.find_element_by_link_text("Create"))\
+ .perform()
+ self.page.find_by_partial_link_text("Server...").click()
+
+ server_config = self.server
+ self.page.fill_input_by_field_name("name", server_config['name'])
+ self.page.find_by_partial_link_text("Connection").click()
+ self.page.fill_input_by_field_name("host", server_config['host'])
+ self.page.fill_input_by_field_name("port", server_config['port'])
+ self.page.fill_input_by_field_name(
+ "username",
+ server_config['username']
+ )
+ self.page.fill_input_by_field_name(
+ "password",
+ server_config['db_password']
+ )
+ self.page.find_by_xpath("//button[contains(.,'Save')]").click()
+
+ def _locate_database_tree_node(self):
+ self.page.toggle_open_tree_item(self.server['name'])
+ self.page.toggle_open_tree_item('Databases')
+ self.page.toggle_open_tree_item('acceptance_test_db')
+
+ def _clear_query_tool(self):
+ # clear codemirror.
+ self.page.find_by_id("btn-edit").click()
+ # wait for alertify dialog open animation to complete.
+ time.sleep(1)
+
+ self.page.click_element(self.page.find_by_xpath("//button[contains(.,'Yes')]"))
+ # wait for alertify dialog close animation to complete.
+ time.sleep(1)
+
+ def _on_demand_result(self):
+ ON_DEMAND_CHUNKS = 2
+ query = """-- On demand query result on scroll
+SELECT generate_series(1, {}) as id""".format(
+ config.ON_DEMAND_RECORD_COUNT * ON_DEMAND_CHUNKS)
+
+ wait = WebDriverWait(self.page.driver, 10)
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-flash").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")))
+
+ # scroll to bottom to fetch next chunk of result set.
+ self.driver.execute_script(
+ "$('.slick-viewport').scrollTop($('.grid-canvas').height());"
+ )
+
+ # wait for ajax to complete.
+ time.sleep(1)
+
+ # again scroll to bottom to bring last row of next chunk in
+ # viewport.
+ self.driver.execute_script(
+ "$('.slick-viewport').scrollTop($('.grid-canvas').height());"
+ )
+
+ row_id_to_find = config.ON_DEMAND_RECORD_COUNT * ON_DEMAND_CHUNKS
+
+ canvas.find_element_by_xpath(
+ '//span[text()="{}"]'.format(row_id_to_find)
+ )
+
+ def _on_demand_result_select_all_grid(self):
+ ON_DEMAND_CHUNKS = 3
+ query = """-- On demand query result on grid select all
+SELECT generate_series(1, {}) as id""".format(
+ config.ON_DEMAND_RECORD_COUNT * ON_DEMAND_CHUNKS)
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-flash").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, ".slick-header-column"))).click()
+
+ # wait for until all records are fetched and selected.
+ time.sleep(1)
+ # scroll to bottom to bring last row of next chunk in
+ # viewport.
+ self.driver.execute_script(
+ "$('.slick-viewport').scrollTop($('.grid-canvas').height());"
+ )
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+
+ row_id_to_find = config.ON_DEMAND_RECORD_COUNT * ON_DEMAND_CHUNKS
+
+ canvas.find_element_by_xpath(
+ '//span[text()="{}"]'.format(row_id_to_find)
+ )
+
+ def _on_demand_result_select_all_column(self):
+ ON_DEMAND_CHUNKS = 4
+ query = """-- On demand query result on column select all
+SELECT generate_series(1, {}) as id1, 'dummy' as id2""".format(
+ config.ON_DEMAND_RECORD_COUNT * ON_DEMAND_CHUNKS)
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-flash").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ # click on first data column to select all column.
+
+ wait.until(EC.presence_of_element_located(
+ (
+ By.XPATH,
+ "//span[contains(@class, 'column-name') and contains(., 'id1')]"))
+ ).click()
+
+ # wait for until all records are fetched and selected.
+ time.sleep(1)
+ # scroll to bottom to bring last row of next chunk in
+ # viewport.
+ self.driver.execute_script(
+ "$('.slick-viewport').scrollTop($('.grid-canvas').height());"
+ )
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+
+ row_id_to_find = config.ON_DEMAND_RECORD_COUNT * ON_DEMAND_CHUNKS
+
+ canvas.find_element_by_xpath(
+ '//span[text()="{}"]'.format(row_id_to_find)
+ )
+
+ def _query_tool_explain(self):
+ query = """-- Explain query
+SELECT generate_series(1, 1000) as id order by id desc"""
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-query-dropdown").click()
+ self.page.find_by_id("btn-explain").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ self.page.click_tab('Data Output')
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+ # Search for Plan word in result
+ canvas.find_element_by_xpath("//*[contains(string(),'Plan')]")
+
+ def _query_tool_explain_verbose(self):
+ query = """-- Explain query with verbose
+SELECT generate_series(1, 1000) as id order by id desc"""
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ query_op = self.page.find_by_id("btn-query-dropdown")
+ query_op.click()
+
+ ActionChains(self.driver).move_to_element(
+ query_op.find_element_by_xpath(
+ "//li[contains(.,'Explain Options')]")).perform()
+
+ self.page.find_by_id("btn-explain-verbose").click()
+
+ self.page.find_by_id("btn-explain").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ self.page.click_tab('Data Output')
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+ # Search for 'Output' word in result
+ canvas.find_element_by_xpath("//*[contains(string(), 'Output')]")
+
+ def _query_tool_explain_cost(self):
+ query = """-- Explain query with costs
+SELECT generate_series(1, 1000) as id order by id desc"""
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+ query_op = self.page.find_by_id("btn-query-dropdown")
+ query_op.click()
+
+ ActionChains(self.driver).move_to_element(
+ query_op.find_element_by_xpath(
+ "//li[contains(.,'Explain Options')]")).perform()
+
+ self.page.find_by_id("btn-explain-costs").click()
+
+ self.page.find_by_id("btn-explain").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ self.page.click_tab('Data Output')
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+ # Search for 'Total Cost word in result
+ canvas.find_element_by_xpath("//*[contains(string(),'Total Cost')]")
+
+ def _query_tool_explain_analyze(self):
+ query = """-- Explain analyze query
+SELECT generate_series(1, 1000) as id order by id desc"""
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-query-dropdown").click()
+ self.page.find_by_id("btn-explain-analyze").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ self.page.click_tab('Data Output')
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+ # Search for Actual Rows word in result
+ canvas.find_element_by_xpath("//*[contains(string(),'Actual Rows')]")
+
+ def _query_tool_explain_analyze_buffers(self):
+ query = """-- Explain analyze query with buffers
+SELECT generate_series(1, 1000) as id order by id desc"""
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ query_op = self.page.find_by_id("btn-query-dropdown")
+ query_op.click()
+
+ ActionChains(self.driver).move_to_element(
+ query_op.find_element_by_xpath(
+ "//li[contains(.,'Explain Options')]")).perform()
+
+ self.page.find_by_id("btn-explain-buffers").click()
+
+ self.page.find_by_id("btn-explain-analyze").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ self.page.click_tab('Data Output')
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+ # Search for 'Shared Read Blocks' word in result
+ canvas.find_element_by_xpath("//*[contains(string(), 'Shared Read Blocks')]")
+
+ def _query_tool_explain_analyze_timing(self):
+ query = """-- Explain analyze query with timing
+SELECT generate_series(1, 1000) as id order by id desc"""
+
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+ query_op = self.page.find_by_id("btn-query-dropdown")
+ query_op.click()
+
+ ActionChains(self.driver).move_to_element(
+ query_op.find_element_by_xpath(
+ "//li[contains(.,'Explain Options')]")).perform()
+
+ self.page.find_by_id("btn-explain-timing").click()
+
+ self.page.find_by_id("btn-explain-analyze").click()
+
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ self.page.click_tab('Data Output')
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))
+ )
+ # Search for 'Actual Total Time' word in result
+ canvas.find_element_by_xpath("//*[contains(string(), 'Actual Total Time')]")
+
+ def _query_tool_auto_commit_disabled(self):
+ table_name = 'query_tool_auto_commit_disabled_table'
+ query = """-- 1. Disable auto commit.
+-- 2. Create table in public schema.
+-- 3. ROLLBACK transaction.
+-- 4. Check if table is *NOT* created.
+CREATE TABLE public.{}();""".format(table_name)
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-query-dropdown").click()
+
+ auto_commit_btn = self.page.find_by_id("btn-auto-commit")
+
+ auto_commit_check = auto_commit_btn.find_element_by_tag_name("i")
+
+ # if auto commit is enabled then 'i' element will
+ # have 'auto-commit fa fa-check' classes
+ # if auto commit is disabled then 'i' element will
+ # have 'auto-commit fa fa-check visibility-hidden' classes
+
+ if 'auto-commit fa fa-check' == str(auto_commit_check.get_attribute(
+ 'class')):
+ auto_commit_btn.click()
+
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "CREATE TABLE")]'
+ )
+
+ self._clear_query_tool()
+ query = """-- 1. (Done) Disable auto commit.
+-- 2. (Done) Create table in public schema.
+-- 3. ROLLBACK transaction.
+-- 4. Check if table is *NOT* created.
+ROLLBACK;"""
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "ROLLBACK")]'
+ )
+
+ self._clear_query_tool()
+ query = """-- 1. (Done) Disable auto commit.
+-- 2. (Done) Create table in public schema.
+-- 3. (Done) ROLLBACK transaction.
+-- 4. Check if table is *NOT* created.
+SELECT relname FROM pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;"""
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Data Output')
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")))
+
+ el = canvas.find_elements_by_xpath("//div[contains(@class, 'slick-cell') and contains(text(), '{}')]".format(table_name))
+
+ assert len(el) == 0, "Table '{}' created with auto commit disabled and without any explicit commit.".format(table_name)
+
+ def _query_tool_auto_commit_enabled(self):
+ table_name = 'query_tool_auto_commit_enabled_table'
+ query = """-- 1. END any open transaction.
+-- 2. Enable auto commit.
+-- 3. Create table in public schema.
+-- 4. ROLLBACK transaction
+-- 5. Check if table is created event after ROLLBACK.
+END;
+CREATE TABLE public.{}();""".format(table_name)
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-query-dropdown").click()
+
+ auto_commit_btn = self.page.find_by_id("btn-auto-commit")
+
+ auto_commit_check = auto_commit_btn.find_element_by_tag_name("i")
+
+ # if auto commit is enabled then 'i' element will
+ # have 'auto-commit fa fa-check' classes
+ # if auto commit is disabled then 'i' element will
+ # have 'auto-commit fa fa-check visibility-hidden' classes
+
+ if 'auto-commit fa fa-check visibility-hidden' == str(auto_commit_check.get_attribute(
+ 'class')):
+ auto_commit_btn.click()
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "CREATE TABLE")]'
+ )
+
+ self._clear_query_tool()
+ query = """-- 1. (Done) END any open transaction if any.
+-- 2. (Done) Enable auto commit.
+-- 3. (Done) Create table in public schema.
+-- 4. ROLLBACK transaction
+-- 5. Check if table is created event after ROLLBACK.
+ROLLBACK;"""
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "ROLLBACK")]'
+ )
+
+ self._clear_query_tool()
+ query = """-- 1. (Done) END any open transaction if any.
+-- 2. (Done) Enable auto commit.
+-- 3. (Done) Create table in public schema.
+-- 4. (Done) ROLLBACK transaction
+-- 5. Check if table is created event after ROLLBACK.
+SELECT relname FROM pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;"""
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-flash").click()
+ self.page.click_tab('Data Output')
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")))
+
+ el = canvas.find_elements_by_xpath("//div[contains(@class, 'slick-cell') and contains(text(), '{}')]".format(table_name))
+
+ assert len(el) != 0, "Table '{}' is not created with auto commit enabled.".format(table_name)
+
+ def _query_tool_auto_rollback_enabled(self):
+ table_name = 'query_tool_auto_rollback_enabled_table'
+ query = """-- 1. END any open transaction.
+-- 2. Enable auto rollback and disable auto commit.
+-- 3. Create table in public schema.
+-- 4. Generate error in transaction.
+-- 5. END transaction.
+-- 6. Check if table is *NOT* created after ending transaction.
+END;"""
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self._clear_query_tool()
+
+ query = """-- 1. (Done) END any open transaction.
+-- 2. Enable auto rollback and disable auto commit.
+-- 3. Create table in public schema.
+-- 4. Generate error in transaction.
+-- 5. END transaction.
+-- 6. Check if table is *NOT* created after ending transaction.
+CREATE TABLE public.{}();""".format(table_name)
+ wait = WebDriverWait(self.page.driver, 10)
+
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-query-dropdown").click()
+
+ auto_rollback_btn = self.page.find_by_id("btn-auto-rollback")
+
+ auto_rollback_check = auto_rollback_btn.find_element_by_tag_name("i")
+
+ # if auto rollback is enabled then 'i' element will
+ # have 'auto-rollback fa fa-check' classes
+ # if auto rollback is disabled then 'i' element will
+ # have 'auto-rollback fa fa-check visibility-hidden' classes
+
+ if 'auto-rollback fa fa-check visibility-hidden' == str(auto_rollback_check.get_attribute(
+ 'class')):
+ auto_rollback_btn.click()
+
+ auto_commit_btn = self.page.find_by_id("btn-auto-commit")
+
+ auto_commit_check = auto_commit_btn.find_element_by_tag_name("i")
+
+ # if auto commit is enabled then 'i' element will
+ # have 'auto-commit fa fa-check' classes
+ # if auto commit is disabled then 'i' element will
+ # have 'auto-commit fa fa-check visibility-hidden' classes
+
+ if 'auto-commit fa fa-check' == str(auto_commit_check.get_attribute(
+ 'class')):
+ auto_commit_btn.click()
+
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "CREATE TABLE")]'
+ )
+
+ self._clear_query_tool()
+ query = """-- 1. (Done) END any open transaction.
+-- 2. (Done) Enable auto rollback and disable auto commit.
+-- 3. (Done) Create table in public schema.
+-- 4. Generate error in transaction.
+-- 5. END transaction.
+-- 6. Check if table is *NOT* created after ending transaction.
+SELECT 1/0;"""
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "division by zero")]'
+ )
+
+ self._clear_query_tool()
+ query = """-- 1. (Done) END any open transaction.
+-- 2. (Done) Enable auto rollback and disable auto commit.
+-- 3. (Done) Create table in public schema.
+-- 4. (Done) Generate error in transaction.
+-- 5. END transaction.
+-- 6. Check if table is *NOT* created after ending transaction.
+END;"""
+
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "Query returned successfully")]'
+ )
+
+ self._clear_query_tool()
+ query = """-- 1. (Done) END any open transaction.
+-- 2. (Done) Enable auto rollback and disable auto commit.
+-- 3. (Done) Create table in public schema.
+-- 4. (Done) Generate error in transaction.
+-- 5. (Done) END transaction.
+-- 6. Check if table is *NOT* created after ending transaction.
+SELECT relname FROM pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;"""
+ self.page.fill_codemirror_area_with(query)
+ self.page.find_by_id("btn-flash").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Data Output')
+ canvas = wait.until(EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")))
+
+ el = canvas.find_elements_by_xpath("//div[contains(@class, 'slick-cell') and contains(text(), '{}')]".format(table_name))
+
+ assert len(el) == 0, "Table '{}' created even after ROLLBACK due to sql error.".format(table_name)
+
+ def _query_tool_cancel_query(self):
+ query = """-- 1. END any open transaction.
+-- 2. Enable auto commit and Disable auto rollback.
+-- 3. Execute long running query.
+-- 4. Cancel long running query execution.
+END;
+SELECT 1, pg_sleep(10)"""
+ self.page.fill_codemirror_area_with(query)
+
+ self.page.find_by_id("btn-query-dropdown").click()
+
+ auto_rollback_btn = self.page.find_by_id("btn-auto-rollback")
+
+ auto_rollback_check = auto_rollback_btn.find_element_by_tag_name("i")
+
+ # if auto rollback is enabled then 'i' element will
+ # have 'auto-rollback fa fa-check' classes
+ # if auto rollback is disabled then 'i' element will
+ # have 'auto-rollback fa fa-check visibility-hidden' classes
+
+ if 'auto-rollback fa fa-check' == str(auto_rollback_check.get_attribute(
+ 'class')):
+ auto_rollback_btn.click()
+
+ auto_commit_btn = self.page.find_by_id("btn-auto-commit")
+
+ auto_commit_check = auto_commit_btn.find_element_by_tag_name("i")
+
+ # if auto commit is enabled then 'i' element will
+ # have 'auto-commit fa fa-check' classes
+ # if auto commit is disabled then 'i' element will
+ # have 'auto-commit fa fa-check visibility-hidden' classes
+
+ if 'auto-commit fa fa-check visibility-hidden' == str(auto_commit_check.get_attribute(
+ 'class')):
+ auto_commit_btn.click()
+
+ self.page.find_by_id("btn-flash").click()
+ self.driver.find_element_by_xpath("//*[@id='fetching_data']")
+ self.page.find_by_id("btn-cancel-query").click()
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+ self.page.click_tab('Messages')
+ self.driver.find_element_by_xpath(
+ '//div[contains(@class, "sql-editor-message") and contains(string(), "canceling statement due to user request")]'
+ )
diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/pgadmin/feature_tests/view_data_dml_queries.py
index 55b45ad14..a6bc5aa77 100644
--- a/web/pgadmin/feature_tests/view_data_dml_queries.py
+++ b/web/pgadmin/feature_tests/view_data_dml_queries.py
@@ -9,6 +9,7 @@
import json
import os
+import time
from selenium.webdriver import ActionChains
from regression.python_test_utils import test_utils
from regression.feature_utils.base_feature_test import BaseFeatureTest
@@ -205,6 +206,10 @@ CREATE TABLE public.defaults
self.page.driver.find_element_by_link_text("View Data")) \
.perform()
self.page.find_by_partial_link_text("View All Rows").click()
+
+ # wait until datagrid frame is loaded.
+ self.page.click_tab('Edit Data -')
+
self.wait.until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR, 'iframe')
@@ -242,6 +247,11 @@ CREATE TABLE public.defaults
self._update_cell(row1_cell2_xpath, ["1", "", "int"])
self.page.find_by_id("btn-save").click() # Save data
+ # There should be some delay after save button is clicked, as it
+ # takes some time to complete save ajax call otherwise discard unsaved
+ # changes dialog will appear if we try to execute query before previous
+ # save ajax is completed.
+ time.sleep(2)
# Verify row 1 and row 2 data
self._verify_row_data(False)
@@ -254,6 +264,11 @@ CREATE TABLE public.defaults
self._update_cell(cell_xpath, config_data[str(idx)])
self.page.find_by_id("btn-save").click() # Save data
+ # There should be some delay after save button is clicked, as it
+ # takes some time to complete save ajax call otherwise discard unsaved
+ # changes dialog will appear if we try to execute query before previous
+ # save ajax is completed.
+ time.sleep(2)
def _verify_row_data(self, is_new_row):
self.page.find_by_id("btn-flash").click()
@@ -264,17 +279,17 @@ CREATE TABLE public.defaults
xpath = "//*[contains(@class, 'ui-widget-content') and " \
"contains(@style, 'top:" + str(row_height) + "px')]"
- # wait for stale element reference exception
- self.page.wait_for_element_to_stale(xpath)
+ self.page.wait_for_query_tool_loading_indicator_to_disappear()
+
result_row = self.page.find_by_xpath(xpath)
# List of row values in an array
cells = [el.text for el in result_row.find_elements_by_tag_name('div')]
for idx in range(1, len(config_data.keys())):
- # # after copy & paste row, the first cell of row 1 and
- # # row 2(being primary keys) won't match
- # # see if cell values matched to actual value
+ # after copy & paste row, the first cell of row 1 and
+ # row 2(being primary keys) won't match
+ # see if cell values matched to actual value
if idx != 1 and not is_new_row:
self.assertEquals(cells[idx], config_data[str(idx)][1])
elif is_new_row:
diff --git a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py
index 4fd078333..75b42220b 100644
--- a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py
+++ b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py
@@ -10,6 +10,9 @@
from selenium.webdriver import ActionChains
from regression.python_test_utils import test_utils
from regression.feature_utils.base_feature_test import BaseFeatureTest
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.common.by import By
import time
class CheckForXssFeatureTest(BaseFeatureTest):
@@ -72,6 +75,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
def _connects_to_server(self):
self.page.find_by_xpath("//*[@class='aciTreeText' and .='Servers']").click()
+ time.sleep(2)
self.page.driver.find_element_by_link_text("Object").click()
ActionChains(self.page.driver) \
.move_to_element(self.page.driver.find_element_by_link_text("Create")) \
@@ -152,11 +156,16 @@ class CheckForXssFeatureTest(BaseFeatureTest):
self.page.fill_codemirror_area_with("select '
'")
time.sleep(1)
self.page.find_by_id("btn-flash").click()
- time.sleep(2)
+ wait = WebDriverWait(self.page.driver, 5)
- source_code = self.page.find_by_xpath(
- "//*[@id='0']//*[@id='datagrid']/div[5]/div/div[1]/div[2]"
- ).get_attribute('innerHTML')
+ result_row = self.page.find_by_xpath(
+ "//*[contains(@class, 'ui-widget-content') and contains(@style, 'top:0px')]"
+ )
+
+ cells = result_row.find_elements_by_tag_name('div')
+
+ # remove first element as it is row number.
+ source_code = cells[1].get_attribute('innerHTML')
self._check_escaped_characters(
source_code,
diff --git a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py
index 094dfed6f..e847040cc 100644
--- a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py
+++ b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py
@@ -42,6 +42,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
def _connects_to_server(self):
self.page.find_by_xpath("//*[@class='aciTreeText' and .='Servers']").click()
+ time.sleep(2)
self.page.driver.find_element_by_link_text("Object").click()
ActionChains(self.page.driver) \
.move_to_element(self.page.driver.find_element_by_link_text("Create")) \
diff --git a/web/pgadmin/static/bundle/slickgrid.js b/web/pgadmin/static/bundle/slickgrid.js
index 0d82a6bd0..dd7f8708b 100644
--- a/web/pgadmin/static/bundle/slickgrid.js
+++ b/web/pgadmin/static/bundle/slickgrid.js
@@ -3,6 +3,7 @@ import 'slickgrid/slick-default-theme.css';
import 'slickgrid/css/smoothness/jquery-ui-1.11.3.custom.css';
import 'slickgrid/slick.core';
import 'slickgrid/slick.grid';
+import 'slickgrid/slick.dataview';
import 'slickgrid/slick.editors';
import 'slickgrid/slick.formatters';
import 'slickgrid/plugins/slick.autotooltips';
diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js
index 305b438d7..f3cb727e0 100644
--- a/web/pgadmin/static/js/selection/column_selector.js
+++ b/web/pgadmin/static/js/selection/column_selector.js
@@ -4,13 +4,18 @@ define([
'slickgrid',
], function ($, RangeSelectionHelper) {
var ColumnSelector = function () {
- var Slick = window.Slick;
- var gridEventBus = new Slick.EventHandler();
+ var Slick = window.Slick,
+ gridEventBus = new Slick.EventHandler(),
+ onBeforeColumnSelectAll = new Slick.Event(),
+ onColumnSelectAll = new Slick.Event();
var init = function (grid) {
gridEventBus.subscribe(grid.onHeaderClick, handleHeaderClick.bind(null, grid));
grid.getSelectionModel().onSelectedRangesChanged
.subscribe(handleSelectedRangesChanged.bind(null, grid));
+ onColumnSelectAll.subscribe(function(e, args) {
+ updateRanges(args.grid, args.column.id);
+ });
};
var handleHeaderClick = function (grid, event, args) {
@@ -21,11 +26,20 @@ define([
if (isColumnSelectable(columnDefinition)) {
var $columnHeader = $(event.target);
if (hasClickedChildOfColumnHeader(event)) {
+ if ($(event.target).hasClass('slick-resizable-handle')) {
+ return;
+ }
$columnHeader = $(event.target).parents('.slick-header-column');
}
$columnHeader.toggleClass('selected');
- updateRanges(grid, columnDefinition.id);
+ if ($columnHeader.hasClass('selected')) {
+ onBeforeColumnSelectAll.notify(args, event);
+ }
+
+ if (!(event.isPropagationStopped() || event.isImmediatePropagationStopped())) {
+ updateRanges(grid, columnDefinition.id);
+ }
}
};
@@ -107,6 +121,8 @@ define([
$.extend(this, {
'init': init,
'getColumnDefinitions': getColumnDefinitions,
+ 'onBeforeColumnSelectAll': onBeforeColumnSelectAll,
+ 'onColumnSelectAll': onColumnSelectAll,
});
};
return ColumnSelector;
diff --git a/web/pgadmin/static/js/selection/copy_data.js b/web/pgadmin/static/js/selection/copy_data.js
index c22632313..35259666a 100644
--- a/web/pgadmin/static/js/selection/copy_data.js
+++ b/web/pgadmin/static/js/selection/copy_data.js
@@ -12,19 +12,19 @@ function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
var grid = self.slickgrid;
var columnDefinitions = grid.getColumns();
var selectedRanges = grid.getSelectionModel().getSelectedRanges();
- var data = grid.getData();
+ var dataView = grid.getData();
var rows = grid.getSelectedRows();
if (RangeSelectionHelper.areAllRangesCompleteRows(grid, selectedRanges)) {
self.copied_rows = rows.map(function (rowIndex) {
- return data[rowIndex];
+ return grid.getDataItem(rowIndex);
});
setPasteRowButtonEnablement(self.can_edit, true);
} else {
self.copied_rows = [];
setPasteRowButtonEnablement(self.can_edit, false);
}
- var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges);
+ var csvText = rangeBoundaryNavigator.rangesToCsv(dataView.getItems(), columnDefinitions, selectedRanges);
if (csvText) {
clipboard.copyTextToClipboard(csvText);
}
diff --git a/web/pgadmin/static/js/selection/grid_selector.js b/web/pgadmin/static/js/selection/grid_selector.js
index 24fd9d634..ddbbe2380 100644
--- a/web/pgadmin/static/js/selection/grid_selector.js
+++ b/web/pgadmin/static/js/selection/grid_selector.js
@@ -6,21 +6,31 @@ define(['jquery',
'sources/url_for',
], function ($, gettext, ColumnSelector, RowSelector, RangeSelectionHelper, url_for) {
var GridSelector = function (columnDefinitions) {
- var rowSelector = new RowSelector(columnDefinitions);
- var columnSelector = new ColumnSelector(columnDefinitions);
+ var Slick = window.Slick,
+ rowSelector = new RowSelector(columnDefinitions),
+ columnSelector = new ColumnSelector(columnDefinitions),
+ onBeforeGridSelectAll = new Slick.Event(),
+ onGridSelectAll = new Slick.Event(),
+ onBeforeGridColumnSelectAll = columnSelector.onBeforeColumnSelectAll,
+ onGridColumnSelectAll = columnSelector.onColumnSelectAll;
var init = function (grid) {
this.grid = grid;
grid.onHeaderClick.subscribe(function (event, eventArguments) {
- if (eventArguments.column.selectAllOnClick) {
- toggleSelectAll(grid);
+ if (eventArguments.column.selectAllOnClick && !$(event.target).hasClass('slick-resizable-handle')) {
+ toggleSelectAll(grid, event, eventArguments);
}
});
grid.getSelectionModel().onSelectedRangesChanged
- .subscribe(handleSelectedRangesChanged.bind(null, grid));
+ .subscribe(handleSelectedRangesChanged.bind(null, grid));
+
grid.registerPlugin(rowSelector);
grid.registerPlugin(columnSelector);
+
+ onGridSelectAll.subscribe(function(e, args) {
+ RangeSelectionHelper.selectAll(args.grid);
+ });
};
var getColumnDefinitions = function (columnDefinitions) {
@@ -45,11 +55,14 @@ define(['jquery',
}
}
- function toggleSelectAll(grid) {
+ function toggleSelectAll(grid, event, eventArguments) {
if (RangeSelectionHelper.isEntireGridSelected(grid)) {
selectNone(grid);
} else {
- RangeSelectionHelper.selectAll(grid);
+ onBeforeGridSelectAll.notify(eventArguments, event);
+ if (!(event.isPropagationStopped() || event.isImmediatePropagationStopped())) {
+ RangeSelectionHelper.selectAll(grid);
+ }
}
}
@@ -61,6 +74,10 @@ define(['jquery',
$.extend(this, {
'init': init,
'getColumnDefinitions': getColumnDefinitions,
+ 'onBeforeGridSelectAll': onBeforeGridSelectAll,
+ 'onGridSelectAll': onGridSelectAll,
+ 'onBeforeGridColumnSelectAll': onBeforeGridColumnSelectAll,
+ 'onGridColumnSelectAll': onGridColumnSelectAll,
});
};
diff --git a/web/pgadmin/static/js/selection/range_boundary_navigator.js b/web/pgadmin/static/js/selection/range_boundary_navigator.js
index 803f75afc..274b6212a 100644
--- a/web/pgadmin/static/js/selection/range_boundary_navigator.js
+++ b/web/pgadmin/static/js/selection/range_boundary_navigator.js
@@ -58,6 +58,7 @@ function (RangeSelectionHelper) {
},
rangesToCsv: function (data, columnDefinitions, selectedRanges) {
+
var rowRangeBounds = selectedRanges.map(function (range) {
return [range.fromRow, range.toRow];
});
@@ -72,6 +73,7 @@ function (RangeSelectionHelper) {
var csvRows = this.mapOver2DArray(rowRangeBounds, colRangeBounds, this.csvCell.bind(this, data, columnDefinitions), function (rowData) {
return rowData.join(',');
});
+
return csvRows.join('\n');
},
@@ -101,7 +103,7 @@ function (RangeSelectionHelper) {
},
csvCell: function (data, columnDefinitions, rowId, colId) {
- var val = data[rowId][columnDefinitions[colId].pos];
+ var val = data[rowId][columnDefinitions[colId].field];
if (val && _.isObject(val)) {
val = '\'' + JSON.stringify(val) + '\'';
diff --git a/web/pgadmin/static/js/selection/row_selector.js b/web/pgadmin/static/js/selection/row_selector.js
index 236ed6291..59d2fa2be 100644
--- a/web/pgadmin/static/js/selection/row_selector.js
+++ b/web/pgadmin/static/js/selection/row_selector.js
@@ -82,7 +82,8 @@ define([
formatter: function (rowIndex) {
return '';
+ 'data-cell-type="row-header-selector">' +
+ (rowIndex+1) + '';
},
width: 30,
});
diff --git a/web/pgadmin/static/js/selection/set_staged_rows.js b/web/pgadmin/static/js/selection/set_staged_rows.js
index 46977e3ae..bc07dd576 100644
--- a/web/pgadmin/static/js/selection/set_staged_rows.js
+++ b/web/pgadmin/static/js/selection/set_staged_rows.js
@@ -22,53 +22,44 @@ define(
$(selector).prop('disabled', false);
}
- function getRowPrimaryKeyValuesToStage(selectedRows, primaryKeyColumnIndices, gridData) {
+ function getRowPrimaryKeyValuesToStage(selectedRows, primaryKeys, dataView, client_primary_key) {
return _.reduce(selectedRows, function (primaryKeyValuesToStage, dataGridRowIndex) {
- var gridRow = gridData[dataGridRowIndex];
-
- if (isRowMissingPrimaryKeys(gridRow, primaryKeyColumnIndices)) {
+ var gridRow = dataView.getItem(dataGridRowIndex);
+ if (isRowMissingPrimaryKeys(gridRow, primaryKeys)) {
return primaryKeyValuesToStage;
}
-
- var tempPK = gridRow.__temp_PK;
- primaryKeyValuesToStage[tempPK] = getSingleRowPrimaryKeyValueToStage(primaryKeyColumnIndices, gridRow);
-
+ var tempPK = gridRow[client_primary_key];
+ primaryKeyValuesToStage[tempPK] = getSingleRowPrimaryKeyValueToStage(primaryKeys, gridRow);
return primaryKeyValuesToStage;
}, {});
}
- function isRowMissingPrimaryKeys(gridRow, primaryKeyColumnIndices) {
+ function isRowMissingPrimaryKeys(gridRow, primaryKeys) {
if (_.isUndefined(gridRow)) {
return true;
}
return !_.isUndefined(
- _.find(primaryKeyColumnIndices, function (pkIndex) {
- return _.isUndefined(gridRow[pkIndex]);
+ _.find(primaryKeys , function (pk) {
+ return _.isUndefined(gridRow[pk]);
})
);
}
- function getSingleRowPrimaryKeyValueToStage(primaryKeyColumnIndices, gridRow) {
+ function getSingleRowPrimaryKeyValueToStage(primaryKeys, gridRow) {
var rowToStage = {};
- if (primaryKeyColumnIndices.length) {
- _.each(_.keys(gridRow), function (columnPos) {
- if (_.contains(primaryKeyColumnIndices, Number(columnPos)))
- rowToStage[columnPos] = gridRow[columnPos];
+ if (primaryKeys && primaryKeys.length) {
+ _.each(_.keys(gridRow), function (columnNames) {
+ if (_.contains(primaryKeys, columnNames))
+ rowToStage[columnNames] = gridRow[columnNames];
});
}
return rowToStage;
}
function getPrimaryKeysForSelectedRows(self, selectedRows) {
- var primaryKeyColumnIndices = _.map(_.keys(self.keys), function (columnName) {
- var columnInfo = _.findWhere(self.columns, {name: columnName});
- return columnInfo['pos'];
- });
-
- var gridData = self.grid.getData();
- var stagedRows = getRowPrimaryKeyValuesToStage(selectedRows, primaryKeyColumnIndices, gridData);
-
+ var dataView = self.grid.getData();
+ var stagedRows = getRowPrimaryKeyValuesToStage(selectedRows, _.keys(self.keys), dataView, self.client_primary_key);
return stagedRows;
}
@@ -114,4 +105,4 @@ define(
};
return setStagedRows;
}
-);
+);
\ No newline at end of file
diff --git a/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js b/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js
index f2dabe06d..15b5fe9b1 100644
--- a/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js
+++ b/web/pgadmin/static/js/slickgrid/slick.pgadmin.editors.js
@@ -76,18 +76,18 @@
last_value = (column_type === 'number') ?
(_.isEmpty(last_value) || last_value) : last_value;
- item[args.column.pos] = state;
+ item[args.column.field] = state;
if (last_value && _.isNull(state) &&
(_.isUndefined(grid.copied_rows[row]) ||
_.isUndefined(grid.copied_rows[row][cell]))
) {
- item[args.column.pos] = undefined;
+ item[args.column.field] = undefined;
if (grid.copied_rows[row] == undefined) grid.copied_rows[row] = [];
grid.copied_rows[row][cell] = 1;
}
}
else {
- item[args.column.pos] = state;
+ item[args.column.field] = state;
}
}
@@ -189,14 +189,14 @@
this.loadValue = function (item) {
var col = args.column;
- if (_.isUndefined(item[args.column.pos]) && col.has_default_val) {
+ if (_.isUndefined(item[args.column.field]) && col.has_default_val) {
$input.val(defaultValue = "");
}
- else if (item[args.column.pos] === "") {
+ else if (item[args.column.field] === "") {
$input.val(defaultValue = "''");
}
else {
- $input.val(defaultValue = item[args.column.pos]);
+ $input.val(defaultValue = item[args.column.field]);
$input.select();
}
};
@@ -323,7 +323,7 @@
};
this.loadValue = function (item) {
- var data = defaultValue = item[args.column.pos];
+ var data = defaultValue = item[args.column.field];
if (data && typeof data === "object" && !Array.isArray(data)) {
data = JSON.stringify(data);
} else if (Array.isArray(data)) {
@@ -443,7 +443,7 @@
};
this.loadValue = function (item) {
- $input.val(defaultValue = item[args.column.pos]);
+ $input.val(defaultValue = item[args.column.field]);
$input.select();
};
@@ -452,7 +452,7 @@
};
this.applyValue = function (item, state) {
- item[args.column.pos] = state;
+ item[args.column.field] = state;
};
this.isValueChanged = function () {
@@ -531,13 +531,13 @@
};
this.loadValue = function (item) {
- defaultValue = item[args.column.pos];
- if (_.isNull(defaultValue)|| _.isUndefined(defaultValue)) {
+ defaultValue = item[args.column.field];
+ if (_.isNull(defaultValue)||_.isUndefined(defaultValue)) {
$select.prop('indeterminate', true);
$select.data('checked', 2);
}
else {
- defaultValue = !!item[args.column.pos];
+ defaultValue = !!item[args.column.field];
if (defaultValue) {
$select.prop('checked', true);
$select.data('checked', 0);
@@ -556,7 +556,7 @@
};
this.applyValue = function (item, state) {
- item[args.column.pos] = state;
+ item[args.column.field] = state;
};
this.isValueChanged = function () {
@@ -648,7 +648,7 @@
};
this.loadValue = function (item) {
- var data = defaultValue = item[args.column.pos];
+ var data = defaultValue = item[args.column.field];
if (typeof data === "object" && !Array.isArray(data)) {
data = JSON.stringify(data);
} else if (Array.isArray(data)) {
@@ -671,7 +671,7 @@
};
this.applyValue = function (item, state) {
- item[args.column.pos] = state;
+ item[args.column.field] = state;
};
this.isValueChanged = function () {
@@ -725,7 +725,7 @@
};
this.loadValue = function (item) {
- var value = item[args.column.pos];
+ var value = item[args.column.field];
// Check if value is null or undefined
if (value === undefined && typeof value === "undefined") {
@@ -858,7 +858,7 @@
};
this.loadValue = function (item) {
- defaultValue = item[args.column.pos];
+ defaultValue = item[args.column.field];
$input.val(defaultValue);
$input[0].defaultValue = defaultValue;
$input.select();
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index 9c0ba0044..c6fcdc331 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -27,7 +27,7 @@ from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
from pgadmin.misc.file_manager import Filemanager
-from config import PG_DEFAULT_DRIVER
+from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT
MODULE_NAME = 'sqleditor'
@@ -82,9 +82,9 @@ class SqlEditorModule(PgAdminModule):
'sqleditor.view_data_start',
'sqleditor.query_tool_start',
'sqleditor.query_tool_preferences',
- 'sqleditor.get_columns',
'sqleditor.poll',
- 'sqleditor.fetch_types',
+ 'sqleditor.fetch',
+ 'sqleditor.fetch_all',
'sqleditor.save',
'sqleditor.get_filter',
'sqleditor.apply_filter',
@@ -261,13 +261,32 @@ def start_view_data(trans_id):
# Check the transaction and connection status
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
+
+ # get the default connection as current connection which is attached to
+ # trans id holds the cursor which has query result so we cannot use that
+ # connection to execute another query otherwise we'll lose query result.
+
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
+ default_conn = manager.connection(did=trans_obj.did)
+
+ # Connect to the Server if not connected.
+ if not default_conn.connected():
+ status, msg = default_conn.connect()
+ if not status:
+ return make_json_response(
+ data={'status': status, 'result': u"{}".format(msg)}
+ )
+
if status and conn is not None \
and trans_obj is not None and session_obj is not None:
try:
+ # set fetched row count to 0 as we are executing query again.
+ trans_obj.update_fetched_row_cnt(0)
+ session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
# Fetch the sql and primary_keys from the object
sql = trans_obj.get_sql()
- pk_names, primary_keys = trans_obj.get_primary_keys()
+ pk_names, primary_keys = trans_obj.get_primary_keys(default_conn)
# Fetch the applied filter.
filter_applied = trans_obj.is_filter_applied()
@@ -338,6 +357,8 @@ def start_query_tool(trans_id):
# Use pickle.loads function to get the command object
session_obj = grid_data[str(trans_id)]
trans_obj = pickle.loads(session_obj['command_obj'])
+ # set fetched row count to 0 as we are executing query again.
+ trans_obj.update_fetched_row_cnt(0)
can_edit = False
can_filter = False
@@ -467,66 +488,6 @@ def preferences(trans_id):
return success_return()
-@blueprint.route(
- '/columns/', methods=["GET"], endpoint='get_columns'
-)
-@login_required
-def get_columns(trans_id):
- """
- This method will returns list of columns of last async query.
-
- Args:
- trans_id: unique transaction id
- """
- columns = dict()
- columns_info = None
- primary_keys = None
- rset = None
- status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
- if status and conn is not None and session_obj is not None:
-
- ver = conn.manager.version
- # Get the template path for the column
- template_path = 'column/sql/#{0}#'.format(ver)
- command_obj = pickle.loads(session_obj['command_obj'])
- if hasattr(command_obj, 'obj_id'):
- SQL = render_template("/".join([template_path,
- 'nodes.sql']),
- tid=command_obj.obj_id)
- # rows with attribute not_null
- status, rset = conn.execute_2darray(SQL)
- if not status:
- return internal_server_error(errormsg=rset)
-
- # Check PK column info is available or not
- if 'primary_keys' in session_obj:
- primary_keys = session_obj['primary_keys']
-
- # Fetch column information
- columns_info = conn.get_column_info()
- if columns_info is not None:
- for key, col in enumerate(columns_info):
- col_type = dict()
- col_type['type_code'] = col['type_code']
- col_type['type_name'] = None
- if rset:
- col_type['not_null'] = col['not_null'] = \
- rset['rows'][key]['not_null']
-
- col_type['has_default_val'] = col['has_default_val'] = \
- rset['rows'][key]['has_default_val']
-
- columns[col['name']] = col_type
-
- # As we changed the transaction object we need to
- # restore it and update the session variable.
- session_obj['columns_info'] = columns
- update_session_grid_transaction(trans_id, session_obj)
-
- return make_json_response(data={'status': True,
- 'columns': columns_info,
- 'primary_keys': primary_keys})
-
@blueprint.route('/poll/', methods=["GET"], endpoint='poll')
@login_required
@@ -539,12 +500,21 @@ def poll(trans_id):
"""
result = None
rows_affected = 0
+ rows_fetched_from = 0
+ rows_fetched_to = 0
+ has_more_rows = False
additional_result = []
+ columns = dict()
+ columns_info = None
+ primary_keys = None
+ types = {}
+ client_primary_key = None
+ rset = None
# Check the transaction and connection status
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
if status and conn is not None and session_obj is not None:
- status, result = conn.poll(formatted_exception_msg=True)
+ status, result = conn.poll(formatted_exception_msg=True, no_result=True)
if not status:
return internal_server_error(result)
elif status == ASYNC_OK:
@@ -559,6 +529,80 @@ def poll(trans_id):
if (trans_status == TX_STATUS_INERROR and
trans_obj.auto_rollback):
conn.execute_void("ROLLBACK;")
+
+ st, result = conn.async_fetchmany_2darray(ON_DEMAND_RECORD_COUNT)
+ if st:
+ if 'primary_keys' in session_obj:
+ primary_keys = session_obj['primary_keys']
+
+ # Fetch column information
+ columns_info = conn.get_column_info()
+ client_primary_key = generate_client_primary_key_name(
+ columns_info
+ )
+ session_obj['client_primary_key'] = client_primary_key
+
+ if columns_info is not None:
+
+ command_obj = pickle.loads(session_obj['command_obj'])
+ if hasattr(command_obj, 'obj_id'):
+ # Get the template path for the column
+ template_path = 'column/sql/#{0}#'.format(
+ conn.manager.version
+ )
+
+ SQL = render_template("/".join([template_path,
+ 'nodes.sql']),
+ tid=command_obj.obj_id)
+ # rows with attribute not_null
+ colst, rset = conn.execute_2darray(SQL)
+ if not colst:
+ return internal_server_error(errormsg=rset)
+
+ for key, col in enumerate(columns_info):
+ col_type = dict()
+ col_type['type_code'] = col['type_code']
+ col_type['type_name'] = None
+ columns[col['name']] = col_type
+
+ if rset:
+ col_type['not_null'] = col['not_null'] = \
+ rset['rows'][key]['not_null']
+
+ col_type['has_default_val'] = \
+ col['has_default_val'] = \
+ rset['rows'][key]['has_default_val']
+
+ if columns:
+ st, types = fetch_pg_types(columns, trans_obj)
+
+ if not st:
+ return internal_server_error(types)
+
+ for col_info in columns.values():
+ for col_type in types:
+ if col_type['oid'] == col_info['type_code']:
+ col_info['type_name'] = col_type['typname']
+
+ session_obj['columns_info'] = columns
+ # status of async_fetchmany_2darray is True and result is none
+ # means nothing to fetch
+ if result and rows_affected > -1:
+ res_len = len(result)
+ if res_len == ON_DEMAND_RECORD_COUNT:
+ has_more_rows = True
+
+ if res_len > 0:
+ rows_fetched_from = trans_obj.get_fetched_row_cnt()
+ trans_obj.update_fetched_row_cnt(rows_fetched_from + res_len)
+ rows_fetched_from += 1
+ rows_fetched_to = trans_obj.get_fetched_row_cnt()
+ session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
+
+ # As we changed the transaction object we need to
+ # restore it and update the session variable.
+ update_session_grid_transaction(trans_id, session_obj)
+
elif status == ASYNC_EXECUTION_ABORTED:
status = 'Cancel'
else:
@@ -599,53 +643,123 @@ def poll(trans_id):
data={
'status': status, 'result': result,
'rows_affected': rows_affected,
- 'additional_messages': additional_messages
+ 'rows_fetched_from': rows_fetched_from,
+ 'rows_fetched_to': rows_fetched_to,
+ 'additional_messages': additional_messages,
+ 'has_more_rows': has_more_rows,
+ 'colinfo': columns_info,
+ 'primary_keys': primary_keys,
+ 'types': types,
+ 'client_primary_key': client_primary_key
}
)
-@blueprint.route(
- '/fetch/types/', methods=["GET"], endpoint='fetch_types'
-)
+@blueprint.route('/fetch/', methods=["GET"], endpoint='fetch')
+@blueprint.route('/fetch//', methods=["GET"], endpoint='fetch_all')
@login_required
-def fetch_pg_types(trans_id):
+def fetch(trans_id, fetch_all=None):
+ result = None
+ has_more_rows = False
+ rows_fetched_from = 0
+ rows_fetched_to = 0
+ fetch_row_cnt = -1 if fetch_all == 1 else ON_DEMAND_RECORD_COUNT
+
+ # Check the transaction and connection status
+ status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
+ if status and conn is not None and session_obj is not None:
+ status, result = conn.async_fetchmany_2darray(fetch_row_cnt)
+ if not status:
+ status = 'Error'
+ else:
+ status = 'Success'
+ res_len = len(result)
+ if fetch_row_cnt != -1 and res_len == ON_DEMAND_RECORD_COUNT:
+ has_more_rows = True
+
+ if res_len:
+ rows_fetched_from = trans_obj.get_fetched_row_cnt()
+ trans_obj.update_fetched_row_cnt(rows_fetched_from + res_len)
+ rows_fetched_from += 1
+ rows_fetched_to = trans_obj.get_fetched_row_cnt()
+ session_obj['command_obj'] = pickle.dumps(trans_obj, -1)
+ update_session_grid_transaction(trans_id, session_obj)
+ else:
+ status = 'NotConnected'
+ result = error_msg
+
+ return make_json_response(
+ data={
+ 'status': status, 'result': result,
+ 'has_more_rows': has_more_rows,
+ 'rows_fetched_from': rows_fetched_from,
+ 'rows_fetched_to': rows_fetched_to
+ }
+ )
+
+
+def fetch_pg_types(columns_info, trans_obj):
"""
This method is used to fetch the pg types, which is required
to map the data type comes as a result of the query.
Args:
- trans_id: unique transaction id
+ columns_info:
"""
- # Check the transaction and connection status
- status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
- if status and conn is not None \
- and trans_obj is not None and session_obj is not None:
- res = {}
- if 'columns_info' in session_obj \
- and session_obj['columns_info'] is not None:
+ # get the default connection as current connection attached to trans id
+ # holds the cursor which has query result so we cannot use that connection
+ # to execute another query otherwise we'll lose query result.
- oids = [session_obj['columns_info'][col]['type_code'] for col in session_obj['columns_info']]
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
+ default_conn = manager.connection(did=trans_obj.did)
- if oids:
- status, res = conn.execute_dict(
- u"""SELECT oid, format_type(oid,null) as typname FROM pg_type WHERE oid IN %s ORDER BY oid;
+ # Connect to the Server if not connected.
+ res = []
+ if not default_conn.connected():
+ status, msg = default_conn.connect()
+ if not status:
+ return status, msg
+
+ oids = [columns_info[col]['type_code'] for col in columns_info]
+
+ if oids:
+ status, res = default_conn.execute_dict(
+ u"""SELECT oid, format_type(oid,null) as typname FROM pg_type WHERE oid IN %s ORDER BY oid;
""", [tuple(oids)])
- if status:
- # iterate through pg_types and update the type name in session object
- for record in res['rows']:
- for col in session_obj['columns_info']:
- type_obj = session_obj['columns_info'][col]
- if type_obj['type_code'] == record['oid']:
- type_obj['type_name'] = record['typname']
+ if not status:
+ return False, res
- update_session_grid_transaction(trans_id, session_obj)
+ return status, res['rows']
else:
- status = False
- res = error_msg
+ return True, []
- return make_json_response(data={'status': status, 'result': res})
+
+def generate_client_primary_key_name(columns_info):
+ temp_key = '__temp_PK'
+ if not columns_info:
+ return temp_key
+
+ initial_temp_key_len = len(temp_key)
+ duplicate = False
+ suffix = 1
+ while 1:
+ for col in columns_info:
+ if col['name'] == temp_key:
+ duplicate = True
+ break
+ if duplicate:
+ if initial_temp_key_len == len(temp_key):
+ temp_key += str(suffix)
+ suffix += 1
+ else:
+ temp_key = temp_key[:-1] + str(suffix)
+ suffix += 1
+ duplicate = False
+ else:
+ break
+ return temp_key
@blueprint.route(
@@ -659,7 +773,6 @@ def save(trans_id):
Args:
trans_id: unique transaction id
"""
-
if request.data:
changed_data = json.loads(request.data, encoding='utf-8')
else:
@@ -669,7 +782,6 @@ def save(trans_id):
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
if status and conn is not None \
and trans_obj is not None and session_obj is not None:
- setattr(trans_obj, 'columns_info', session_obj['columns_info'])
# If there is no primary key found then return from the function.
if len(session_obj['primary_keys']) <= 0 or len(changed_data) <= 0:
@@ -680,7 +792,22 @@ def save(trans_id):
}
)
- status, res, query_res, _rowid = trans_obj.save(changed_data)
+ manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
+ default_conn = manager.connection(did=trans_obj.did)
+
+ # Connect to the Server if not connected.
+ if not default_conn.connected():
+ status, msg = default_conn.connect()
+ if not status:
+ return make_json_response(
+ data={'status': status, 'result': u"{}".format(msg)}
+ )
+
+ status, res, query_res, _rowid = trans_obj.save(
+ changed_data,
+ session_obj['columns_info'],
+ session_obj['client_primary_key'],
+ default_conn)
else:
status = False
res = error_msg
diff --git a/web/pgadmin/tools/sqleditor/command.py b/web/pgadmin/tools/sqleditor/command.py
index b7d8a78e8..3b7efd0b2 100644
--- a/web/pgadmin/tools/sqleditor/command.py
+++ b/web/pgadmin/tools/sqleditor/command.py
@@ -258,7 +258,21 @@ class SQLFilter(object):
return status, result
-class GridCommand(BaseCommand, SQLFilter):
+class FetchedRowTracker(object):
+ """
+ Keeps track of fetched row count.
+ """
+ def __init__(self, **kwargs):
+ self.fetched_rows = 0
+
+ def get_fetched_row_cnt(self):
+ return self.fetched_rows
+
+ def update_fetched_row_cnt(self, rows_cnt):
+ self.fetched_rows = rows_cnt
+
+
+class GridCommand(BaseCommand, SQLFilter, FetchedRowTracker):
"""
class GridCommand(object)
@@ -290,6 +304,7 @@ class GridCommand(BaseCommand, SQLFilter):
"""
BaseCommand.__init__(self, **kwargs)
SQLFilter.__init__(self, **kwargs)
+ FetchedRowTracker.__init__(self, **kwargs)
# Save the connection id, command type
self.conn_id = kwargs['conn_id'] if 'conn_id' in kwargs else None
@@ -299,10 +314,10 @@ class GridCommand(BaseCommand, SQLFilter):
if self.cmd_type == VIEW_FIRST_100_ROWS or self.cmd_type == VIEW_LAST_100_ROWS:
self.limit = 100
- def get_primary_keys(self):
+ def get_primary_keys(self, *args, **kwargs):
return None, None
- def save(self, changed_data):
+ def save(self, changed_data, default_conn=None):
return forbidden(errmsg=gettext("Data cannot be saved for the current object."))
def get_limit(self):
@@ -340,14 +355,14 @@ class TableCommand(GridCommand):
# call base class init to fetch the table name
super(TableCommand, self).__init__(**kwargs)
- def get_sql(self):
+ def get_sql(self, default_conn=None):
"""
This method is used to create a proper SQL query
to fetch the data for the specified table
"""
# Fetch the primary keys for the table
- pk_names, primary_keys = self.get_primary_keys()
+ pk_names, primary_keys = self.get_primary_keys(default_conn)
sql_filter = self.get_filter()
@@ -362,13 +377,16 @@ class TableCommand(GridCommand):
return sql
- def get_primary_keys(self):
+ def get_primary_keys(self, default_conn=None):
"""
This function is used to fetch the primary key columns.
"""
driver = get_driver(PG_DEFAULT_DRIVER)
- manager = driver.connection_manager(self.sid)
- conn = manager.connection(did=self.did, conn_id=self.conn_id)
+ if default_conn is None:
+ manager = driver.connection_manager(self.sid)
+ conn = manager.connection(did=self.did, conn_id=self.conn_id)
+ else:
+ conn = default_conn
pk_names = ''
primary_keys = OrderedDict()
@@ -400,7 +418,11 @@ class TableCommand(GridCommand):
def can_filter(self):
return True
- def save(self, changed_data):
+ def save(self,
+ changed_data,
+ columns_info,
+ client_primary_key='__temp_PK',
+ default_conn=None):
"""
This function is used to save the data into the database.
Depending on condition it will either update or insert the
@@ -408,10 +430,16 @@ class TableCommand(GridCommand):
Args:
changed_data: Contains data to be saved
+ columns_info:
+ default_conn:
+ client_primary_key:
"""
-
- manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(self.sid)
- conn = manager.connection(did=self.did, conn_id=self.conn_id)
+ driver = get_driver(PG_DEFAULT_DRIVER)
+ if default_conn is None:
+ manager = driver.connection_manager(self.sid)
+ conn = manager.connection(did=self.did, conn_id=self.conn_id)
+ else:
+ conn = default_conn
status = False
res = None
@@ -421,14 +449,6 @@ class TableCommand(GridCommand):
list_of_sql = []
_rowid = None
- # Replace column positions with names
- def set_column_names(data):
- new_data = {}
- for key in data:
- new_data[changed_data['columns'][int(key)]['name']] = data[key]
-
- return new_data
-
if conn.connected():
# Start the transaction
@@ -443,6 +463,20 @@ class TableCommand(GridCommand):
if len(changed_data[of_type]) < 1:
continue
+ column_type = {}
+ for each_col in columns_info:
+ if (
+ columns_info[each_col]['not_null'] and
+ not columns_info[each_col][
+ 'has_default_val']
+ ):
+ column_data[each_col] = None
+ column_type[each_col] =\
+ columns_info[each_col]['type_name']
+ else:
+ column_type[each_col] = \
+ columns_info[each_col]['type_name']
+
# For newly added rows
if of_type == 'added':
@@ -451,37 +485,18 @@ class TableCommand(GridCommand):
# no_default_value, set column to blank, instead
# of not null which is set by default.
column_data = {}
- column_type = {}
pk_names, primary_keys = self.get_primary_keys()
- for each_col in self.columns_info:
- if (
- self.columns_info[each_col]['not_null'] and
- not self.columns_info[each_col][
- 'has_default_val']
- ):
- column_data[each_col] = None
- column_type[each_col] =\
- self.columns_info[each_col]['type_name']
- else:
- column_type[each_col] = \
- self.columns_info[each_col]['type_name']
-
-
for each_row in changed_data[of_type]:
data = changed_data[of_type][each_row]['data']
# Remove our unique tracking key
- data.pop('__temp_PK', None)
+ data.pop(client_primary_key, None)
data.pop('is_row_copied', None)
- data = set_column_names(data)
- data_type = set_column_names(changed_data[of_type][each_row]['data_type'])
- list_of_rowid.append(data.get('__temp_PK'))
+ list_of_rowid.append(data.get(client_primary_key))
- # Update columns value and data type
- # with columns having not_null=False and has
- # no default value
+ # Update columns value with columns having
+ # not_null=False and has no default value
column_data.update(data)
- column_type.update(data_type)
sql = render_template("/".join([self.sql_path, 'insert.sql']),
data_to_be_saved=column_data,
@@ -497,15 +512,14 @@ class TableCommand(GridCommand):
# For updated rows
elif of_type == 'updated':
for each_row in changed_data[of_type]:
- data = set_column_names(changed_data[of_type][each_row]['data'])
- pk = set_column_names(changed_data[of_type][each_row]['primary_keys'])
- data_type = set_column_names(changed_data[of_type][each_row]['data_type'])
+ data = changed_data[of_type][each_row]['data']
+ pk = changed_data[of_type][each_row]['primary_keys']
sql = render_template("/".join([self.sql_path, 'update.sql']),
data_to_be_saved=data,
primary_keys=pk,
object_name=self.object_name,
nsp_name=self.nsp_name,
- data_type=data_type)
+ data_type=column_type)
list_of_sql.append(sql)
list_of_rowid.append(data)
@@ -519,18 +533,19 @@ class TableCommand(GridCommand):
rows_to_delete.append(changed_data[of_type][each_row])
# Fetch the keys for SQL generation
if is_first:
- # We need to covert dict_keys to normal list in Python3
- # In Python2, it's already a list & We will also fetch column names using index
- keys = [
- changed_data['columns'][int(k)]['name']
- for k in list(changed_data[of_type][each_row].keys())
- ]
+ # We need to covert dict_keys to normal list in
+ # Python3
+ # In Python2, it's already a list & We will also
+ # fetch column names using index
+ keys = list(changed_data[of_type][each_row].keys())
+
no_of_keys = len(keys)
is_first = False
# Map index with column name for each row
for row in rows_to_delete:
for k, v in row.items():
- # Set primary key with label & delete index based mapped key
+ # Set primary key with label & delete index based
+ # mapped key
try:
row[changed_data['columns'][int(k)]['name']] = v
except ValueError:
@@ -597,7 +612,7 @@ class ViewCommand(GridCommand):
# call base class init to fetch the table name
super(ViewCommand, self).__init__(**kwargs)
- def get_sql(self):
+ def get_sql(self, default_conn=None):
"""
This method is used to create a proper SQL query
to fetch the data for the specified view
@@ -652,7 +667,7 @@ class ForeignTableCommand(GridCommand):
# call base class init to fetch the table name
super(ForeignTableCommand, self).__init__(**kwargs)
- def get_sql(self):
+ def get_sql(self, default_conn=None):
"""
This method is used to create a proper SQL query
to fetch the data for the specified foreign table
@@ -697,7 +712,7 @@ class CatalogCommand(GridCommand):
# call base class init to fetch the table name
super(CatalogCommand, self).__init__(**kwargs)
- def get_sql(self):
+ def get_sql(self, default_conn=None):
"""
This method is used to create a proper SQL query
to fetch the data for the specified catalog object
@@ -722,7 +737,7 @@ class CatalogCommand(GridCommand):
return True
-class QueryToolCommand(BaseCommand):
+class QueryToolCommand(BaseCommand, FetchedRowTracker):
"""
class QueryToolCommand(BaseCommand)
@@ -732,13 +747,15 @@ class QueryToolCommand(BaseCommand):
def __init__(self, **kwargs):
# call base class init to fetch the table name
- super(QueryToolCommand, self).__init__(**kwargs)
+
+ BaseCommand.__init__(self, **kwargs)
+ FetchedRowTracker.__init__(self, **kwargs)
self.conn_id = None
self.auto_rollback = False
self.auto_commit = True
- def get_sql(self):
+ def get_sql(self, default_conn=None):
return None
def can_edit(self):
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index 7266b346b..90ae25506 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -423,7 +423,7 @@ input.editor-checkbox:focus {
/* To highlight all newly inserted rows */
.grid-canvas .new_row {
- background: #dff0d7;
+ background: #dff0d7 !important;
}
/* To highlight all the updated rows */
@@ -433,7 +433,7 @@ input.editor-checkbox:focus {
/* To highlight row at fault */
.grid-canvas .new_row.error, .grid-canvas .updated_row.error {
- background: #f2dede;
+ background: #f2dede !important;
}
/* Disabled row */
@@ -460,6 +460,11 @@ input.editor-checkbox:focus {
background-color: #2C76B4;
}
+.slick-cell span[data-cell-type="row-header-selector"] {
+ display: block;
+ text-align: right;
+}
+
#datagrid div.slick-header.ui-state-default {
background: #ffffff;
border-bottom: none;
@@ -481,7 +486,9 @@ input.editor-checkbox:focus {
.select-all-icon {
margin-left: 9px;
+ margin-right: 9px;
vertical-align: bottom;
+ float: right;
}
.slick-cell, .slick-headerrow-column {
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index cac2e5506..eeb320e9b 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -463,7 +463,7 @@ define([
- staged_rows:
This will hold all the data which user copies/pastes/deletes in grid
- deleted:
- This will hold all the data which user delets in grid
+ This will hold all the data which user deletes in grid
Events handling:
----------------
@@ -479,34 +479,10 @@ define([
- We are using this event for Copy operation on grid
*/
- // Get the item column value using a custom 'fieldIdx' column param
- get_item_column_value: function (item, column) {
- if (column.pos !== undefined) {
- return item[column.pos];
- } else {
- return null;
- }
- },
-
// This function is responsible to create and render the SlickGrid.
- render_grid: function(collection, columns, is_editable) {
+ render_grid: function(collection, columns, is_editable, client_primary_key, rows_affected) {
var self = this;
- // returns primary keys
- self.handler.get_row_primary_key = function() {
- var self = this,
- tmp_keys = [];
- _.each(self.primary_keys, function(p, idx) {
- // For each columns search primary key position
- _.each(self.columns, function(c) {
- if(c.name == idx) {
- tmp_keys.push(c.pos);
- }
- });
- });
- return tmp_keys;
- };
-
// This will work as data store and holds all the
// inserted/updated/deleted data from grid
self.handler.data_store = {
@@ -521,8 +497,9 @@ define([
// To store primary keys before they gets changed
self.handler.primary_keys_data = {};
- // Add getItemMetadata into handler for later use
- self.handler.data_view = collection;
+ self.client_primary_key = client_primary_key;
+
+ self.client_primary_key_counter = 0;
// Remove any existing grid first
if (self.handler.slickgrid) {
@@ -601,7 +578,12 @@ define([
});
var gridSelector = new GridSelector();
- grid_columns = gridSelector.getColumnDefinitions(grid_columns);
+ grid_columns = self.grid_columns = gridSelector.getColumnDefinitions(grid_columns);
+
+ if (rows_affected) {
+ // calculate with for header row column.
+ grid_columns[0]['width'] = SqlEditorUtils.calculateColumnWidth(rows_affected);
+ }
var grid_options = {
editable: true,
@@ -609,8 +591,7 @@ define([
enableCellNavigation: true,
enableColumnReorder: false,
asyncEditorLoading: false,
- autoEdit: false,
- dataItemColumnValueExtractor: this.get_item_column_value
+ autoEdit: false
};
var $data_grid = self.$el.find('#datagrid');
@@ -618,17 +599,15 @@ define([
var grid_height = $($('#editor-panel').find('.wcFrame')[1]).height() - 35;
$data_grid.height(grid_height);
- // Add our own custom primary key to keep track of changes
- _.each(collection, function(row){
- row['__temp_PK'] = SqlEditorUtils.epicRandomString(15);
- });
+ var dataView = self.dataView = new Slick.Data.DataView(),
+ grid = self.grid = new Slick.Grid($data_grid, dataView, grid_columns, grid_options);
// Add-on function which allow us to identify the faulty row after insert/update
// and apply css accordingly
- collection.getItemMetadata = function(i) {
- var res = {},
- cssClass = '',
- data_store = self.handler.data_store;
+
+ dataView.getItemMetadata = function(i) {
+ var res = {}, cssClass = '',
+ data_store = self.handler.data_store;
if (_.has(self.handler, 'data_store')) {
if (i in data_store.added_index &&
@@ -651,9 +630,8 @@ define([
cssClass += ' disabled_row';
}
return {'cssClasses': cssClass};
- }
+ };
- var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options);
grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) );
grid.registerPlugin(new ActiveCellCapture());
grid.setSelectionModel(new XCellSelectionModel());
@@ -665,7 +643,8 @@ define([
columns: columns,
grid: grid,
selection: grid.getSelectionModel(),
- editor: self
+ editor: self,
+ client_primary_key: self.client_primary_key
};
self.handler.slickgrid = grid;
@@ -684,6 +663,42 @@ define([
});
});
+ gridSelector.onBeforeGridSelectAll.subscribe(function(e, args) {
+ if (self.handler.has_more_rows) {
+ // this will prevent selection un-till we load all data
+ e.stopImmediatePropagation();
+ self.fetch_next_all(function() {
+ // since we've stopped event propagation we need to
+ // trigger onGridSelectAll manually with new event data.
+ gridSelector.onGridSelectAll.notify(args, new Slick.EventData());
+ });
+ }
+ });
+
+ gridSelector.onBeforeGridColumnSelectAll.subscribe(function(e, args) {
+ if (self.handler.has_more_rows) {
+ // this will prevent selection un-till we load all data
+ e.stopImmediatePropagation();
+ self.fetch_next_all(function() {
+ // since we've stopped event propagation we need to
+ // trigger onGridColumnSelectAll manually with new event data.
+ gridSelector.onGridColumnSelectAll.notify(args, new Slick.EventData());
+ });
+ }
+ });
+
+ // listen for row count change.
+ dataView.onRowCountChanged.subscribe(function (e, args) {
+ grid.updateRowCount();
+ grid.render();
+ });
+
+ // listen for rows change.
+ dataView.onRowsChanged.subscribe(function (e, args) {
+ grid.invalidateRows(args.rows);
+ grid.render();
+ });
+
// Listener function which will be called before user updates existing cell
// This will be used to collect primary key for that row
grid.onBeforeEditCell.subscribe(function (e, args) {
@@ -696,8 +711,8 @@ define([
return false;
}
- if(self.handler.can_edit && before_data && '__temp_PK' in before_data) {
- var _pk = before_data.__temp_PK,
+ if(self.handler.can_edit && before_data && self.client_primary_key in before_data) {
+ var _pk = before_data[self.client_primary_key],
_keys = self.handler.primary_keys,
current_pk = {}, each_pk_key = {};
@@ -709,22 +724,34 @@ define([
// Fetch primary keys for the row before they gets modified
var _columns = self.handler.columns;
_.each(_keys, function(value, key) {
- pos = _.where(_columns, {name: key})[0]['pos']
- current_pk[pos] = before_data[pos];
+ current_pk[key] = before_data[key];
});
// Place it in main variable for later use
self.handler.primary_keys_data[_pk] = current_pk
}
});
- grid.onKeyDown.subscribe(handleQueryOutputKeyboardEvent);
+ grid.onKeyDown.subscribe(function(event, args) {
+ var KEY_A = 65;
+ var modifiedKey = event.keyCode;
+ var isModifierDown = event.ctrlKey || event.metaKey;
+ // Intercept Ctrl/Cmd + A key board event.
+ // As we might want to load all rows before selecting all.
+ if (isModifierDown && modifiedKey == KEY_A && self.handler.has_more_rows) {
+ self.fetch_next_all(function() {
+ handleQueryOutputKeyboardEvent(event, args);
+ });
+ } else {
+ handleQueryOutputKeyboardEvent(event, args);
+ }
+ });
// Listener function which will be called when user updates existing rows
grid.onCellChange.subscribe(function (e, args) {
// self.handler.data_store.updated will holds all the updated data
- var changed_column = args.grid.getColumns()[args.cell].pos, // Current field pos
+ var changed_column = args.grid.getColumns()[args.cell].field,
updated_data = args.item[changed_column], // New value for current field
- _pk = args.item.__temp_PK || null, // Unique key to identify row
+ _pk = args.item[self.client_primary_key] || null, // Unique key to identify row
column_data = {},
_type;
@@ -734,11 +761,16 @@ define([
// so that cell edit is enabled for that row.
var grid = args.grid,
row_data = grid.getDataItem(args.row),
- p_keys_list = _.pick(
- row_data, self.handler.get_row_primary_key()
- ),
- is_primary_key = Object.keys(p_keys_list).length ?
- p_keys_list[0] : undefined;
+ is_primary_key = _.all(
+ _.values(
+ _.pick(
+ row_data, self.primary_keys
+ )
+ ),
+ function(val) {
+ return val != undefined
+ }
+ );
// temp_new_rows is available only for view data.
if (is_primary_key && self.handler.temp_new_rows) {
@@ -758,7 +790,6 @@ define([
column_data);
//Find type for current column
self.handler.data_store.added[_pk]['err'] = false
- self.handler.data_store.added[_pk]['data_type'][changed_column] = _.where(this.columns, {pos: changed_column})[0]['type'];
// Check if it is updated data from existing rows?
} else if(_pk in self.handler.data_store.updated) {
_.extend(
@@ -766,9 +797,6 @@ define([
column_data
);
self.handler.data_store.updated[_pk]['err'] = false
-
- //Find type for current column
- self.handler.data_store.updated[_pk]['data_type'][changed_column] = _.where(this.columns, {pos: changed_column})[0]['type'];
} else {
// First updated data for this primary key
self.handler.data_store.updated[_pk] = {
@@ -776,32 +804,19 @@ define([
'primary_keys': self.handler.primary_keys_data[_pk]
};
self.handler.data_store.updated_index[args.row] = _pk;
- // Find & add column data type for current changed column
- var temp = {};
- temp[changed_column] = _.where(this.columns, {pos: changed_column})[0]['type'];
- self.handler.data_store.updated[_pk]['data_type'] = temp;
}
}
// Enable save button
$("#btn-save").prop('disabled', false);
}.bind(editor_data));
- grid.addBlankRow = function() {
- // Add a blank row in the end of grid
- this.setData(this.getData(), true);
- this.updateRowCount();
- this.invalidateAllRows();
- this.render();
- };
-
// Listener function which will be called when user adds new rows
grid.onAddNewRow.subscribe(function (e, args) {
// self.handler.data_store.added will holds all the newly added rows/data
- var _key = SqlEditorUtils.epicRandomString(10),
- column = args.column,
- item = args.item,
- data_length = this.grid.getDataLength(),
- new_collection = args.grid.getData();
+ var column = args.column,
+ item = args.item, data_length = this.grid.getDataLength(),
+ _key = (self.client_primary_key_counter++).toString(),
+ dataView = this.grid.getData();
// Add new row in list to keep track of it
if (_.isUndefined(item[0])) {
@@ -810,29 +825,32 @@ define([
// If copied item has already primary key, use it.
if(item) {
- item.__temp_PK = _key;
+ item[self.client_primary_key] = _key;
}
- new_collection.push(item);
+ dataView.addItem(item);
self.handler.data_store.added[_key] = {'err': false, 'data': item};
self.handler.data_store.added_index[data_length] = _key;
// Fetch data type & add it for the column
var temp = {};
- temp[column.pos] = _.where(this.columns, {pos: column.pos})[0]['type'];
- self.handler.data_store.added[_key]['data_type'] = temp;
- grid.invalidateRows([new_collection.length - 1]);
+ temp[column.name] = _.where(this.columns, {pos: column.pos})[0]['type'];
grid.updateRowCount();
grid.render();
- // Add a blank row after add row
- if (!args.is_copy_row) {
- grid.addBlankRow();
- }
-
// Enable save button
$("#btn-save").prop('disabled', false);
}.bind(editor_data));
+ // Listen grid viewportChanged event to load next chunk of data.
+ grid.onViewportChanged.subscribe(function(e, args) {
+ var rendered_range = args.grid.getRenderedRange(),
+ data_len = args.grid.getDataLength();
+ // start fetching next batch of records before reaching to bottom.
+ if (self.handler.has_more_rows && !self.handler.fetching_rows && rendered_range.bottom > data_len - 100) {
+ // fetch asynchronous
+ setTimeout(self.fetch_next.bind(self));
+ }
+ })
// Resize SlickGrid when window resize
$( window ).resize( function() {
// Resize grid only when 'Data Output' panel is visible.
@@ -855,6 +873,87 @@ define([
if(self.data_output_panel.isVisible())
self.grid_resize(grid);
});
+
+ for (var i = 0; i < collection.length; i++) {
+ // Convert to dict from 2darray
+ var item = {};
+ for (var j = 1; j < grid_columns.length; j++) {
+ item[grid_columns[j]['field']] = collection[i][grid_columns[j]['pos']]
+ }
+
+ item[self.client_primary_key] = (self.client_primary_key_counter++).toString();
+ collection[i] = item;
+ }
+ dataView.setItems(collection, self.client_primary_key);
+ },
+ fetch_next_all(cb) {
+ this.fetch_next(true, cb);
+ },
+ fetch_next: function(fetch_all, cb) {
+ var self = this, url = '';
+
+ // This will prevent fetch operation if previous fetch operation is
+ // already in progress.
+ self.handler.fetching_rows = true;
+
+ $("#btn-flash").prop('disabled', true);
+
+ if (fetch_all) {
+ self.handler.trigger(
+ 'pgadmin-sqleditor:loading-icon:show',
+ gettext('Fetching all records...')
+ );
+ url = url_for('sqleditor.fetch_all', {'trans_id': self.transId, 'fetch_all': 1});
+ } else {
+ url = url = url_for('sqleditor.fetch', {'trans_id': self.transId});
+ }
+
+ $.ajax({
+ url: url,
+ method: 'GET',
+ success: function(res) {
+ self.handler.has_more_rows = res.data.has_more_rows;
+ $("#btn-flash").prop('disabled', false);
+ self.handler.trigger('pgadmin-sqleditor:loading-icon:hide');
+ self.update_grid_data(res.data.result);
+ self.handler.fetching_rows = false;
+ if (typeof cb == "function") {
+ cb();
+ }
+ },
+ error: function(e) {
+ $("#btn-flash").prop('disabled', false);
+ self.handler.trigger('pgadmin-sqleditor:loading-icon:hide');
+ self.handler.has_more_rows = false;
+ self.handler.fetching_rows = false;
+ if (typeof cb == "function") {
+ cb();
+ }
+ if (e.readyState == 0) {
+ self.update_msg_history(false,
+ gettext('Not connected to the server or the connection to the server has been closed.')
+ );
+ return;
+ }
+ }
+ });
+ },
+
+ update_grid_data: function(data) {
+ this.dataView.beginUpdate();
+
+ for (var i = 0; i < data.length; i++) {
+ // Convert 2darray to dict.
+ var item = {};
+ for (var j = 1; j < this.grid_columns.length; j++) {
+ item[this.grid_columns[j]['field']] = data[i][this.grid_columns[j]['pos']]
+ }
+
+ item[this.client_primary_key] = (this.client_primary_key_counter++).toString();
+ this.dataView.addItem(item);
+ }
+
+ this.dataView.endUpdate();
},
/* This function is responsible to render output grid */
@@ -1396,6 +1495,8 @@ define([
self.explain_buffers = false;
self.explain_timing = false;
self.is_new_browser_tab = is_new_browser_tab;
+ self.has_more_rows = false;
+ self.fetching_rows = false;
// We do not allow to call the start multiple times.
if (self.gridView)
@@ -1503,6 +1604,8 @@ define([
self.rows_to_disable = new Array();
// Temporarily hold new rows added
self.temp_new_rows = new Array();
+ self.has_more_rows = false;
+ self.fetching_rows = false;
self.trigger(
'pgadmin-sqleditor:loading-icon:show',
@@ -1581,45 +1684,14 @@ define([
});
},
- // This function makes the ajax call to fetch columns for last async query,
- get_columns: function(poll_result) {
- var self = this;
- // Check the flag and decide if we need to fetch columns from server
- // or use the columns data stored locally from previous call?
- if (self.FETCH_COLUMNS_FROM_SERVER) {
- $.ajax({
- url: url_for('sqleditor.get_columns', {'trans_id': self.transId}),
- method: 'GET',
- success: function(res) {
- poll_result.colinfo = res.data.columns;
- poll_result.primary_keys = res.data.primary_keys;
- self.call_render_after_poll(poll_result);
- // Set a flag to get columns to false & set the value for future use
- self.FETCH_COLUMNS_FROM_SERVER = false;
- self.COLUMNS_DATA = res;
- },
- error: function(e) {
- var msg = e.responseText;
- if (e.responseJSON != undefined && e.responseJSON.errormsg != undefined)
- msg = e.responseJSON.errormsg;
- alertify.error(msg, 5);
- }
- });
- } else {
- // Use the previously saved columns data
- poll_result.colinfo = self.COLUMNS_DATA.data.columns;
- poll_result.primary_keys = self.COLUMNS_DATA.data.primary_keys;
- self.call_render_after_poll(poll_result);
- }
- },
-
// This is a wrapper to call _render function
// We need this because we have separated columns route & result route
// We need to combine both result here in wrapper before rendering grid
call_render_after_poll: function(res) {
var self = this;
self.query_end_time = new Date();
- self.rows_affected = res.rows_affected;
+ self.rows_affected = res.rows_affected,
+ self.has_more_rows = res.has_more_rows;
/* If no column information is available it means query
runs successfully with no result to display. In this
@@ -1668,7 +1740,8 @@ define([
'pgadmin-sqleditor:loading-icon:message',
gettext("Loading data from the database server and rendering...")
);
- self.get_columns(res.data);
+
+ self.call_render_after_poll(res.data);
}
else if (res.data.status === 'Busy') {
// If status is Busy then poll the result by recursive call to the poll function
@@ -1728,6 +1801,7 @@ define([
var self = this;
self.colinfo = data.col_info;
self.primary_keys = data.primary_keys;
+ self.client_primary_key = data.client_primary_key;
self.cell_selected = false;
self.selected_model = null;
self.changedModels = [];
@@ -1780,7 +1854,7 @@ define([
self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time);
self.update_msg_history(true, "", false);
var msg1 = S(gettext("Total query runtime: %s.")).sprintf(self.total_time).value();
- var msg2 = S(gettext("%s rows retrieved.")).sprintf(self.rows_affected).value();
+ var msg2 = S(gettext("%s rows affected.")).sprintf(self.rows_affected).value();
// Display the notifier if the timeout is set to >= 0
if (self.info_notifier_timeout >= 0) {
@@ -1816,7 +1890,8 @@ define([
setTimeout(
function() {
self.gridView.render_grid(
- explain_data_array, self.columns, self.can_edit
+ explain_data_array, self.columns, self.can_edit,
+ self.client_primary_key
);
// Make sure - the 'Explain' panel is visible, before - we
// start rendering the grid.
@@ -1832,7 +1907,8 @@ define([
self.gridView.data_output_panel.focus();
setTimeout(
function() {
- self.gridView.render_grid(data.result, self.columns, self.can_edit);
+ self.gridView.render_grid(data.result, self.columns,
+ self.can_edit, self.client_primary_key, data.rows_affected);
}, 10
);
}
@@ -1840,138 +1916,114 @@ define([
// Hide the loading icon
self.trigger('pgadmin-sqleditor:loading-icon:hide');
$("#btn-flash").prop('disabled', false);
- }.bind(self),
- function() {
- this.trigger('pgadmin-sqleditor:loading-icon:hide');
- $("#btn-flash").prop('disabled', false);
}.bind(self)
);
},
// This function creates the columns as required by the backgrid
- _fetch_column_metadata: function(data, cb, _fail) {
+ _fetch_column_metadata: function(data, cb) {
var colinfo = data.colinfo,
primary_keys = data.primary_keys,
result = data.result,
columns = [],
self = this;
-
- self.trigger(
- 'pgadmin-sqleditor:loading-icon:message',
- gettext("Retrieving information about the columns returned...")
- );
-
- // Make ajax call to fetch the pg types to map numeric data type
- $.ajax({
- url: url_for('sqleditor.fetch_types', {'trans_id': self.transId}),
- method: 'GET',
- success: function(res) {
- if (res.data.status) {
- // Store pg_types in an array
- var pg_types = new Array();
- _.each(res.data.result.rows, function(r) {
- pg_types[r.oid] = [r.typname];
- });
-
- // Create columns required by backgrid to render
- _.each(colinfo, function(c) {
- var is_primary_key = false;
-
- // Check whether table have primary key
- if (_.size(primary_keys) > 0) {
- _.each(primary_keys, function (value, key) {
- if (key === c.name)
- is_primary_key = true;
- });
- }
-
- // To show column label and data type in multiline,
- // The elements should be put inside the div.
- // Create column label and type.
- var col_type = '',
- column_label = '',
- col_cell;
- var type = pg_types[c.type_code] ?
- pg_types[c.type_code][0] :
- // This is the case where user might
- // have use casting so we will use type
- // returned by cast function
- pg_types[pg_types.length - 1][0] ?
- pg_types[pg_types.length - 1][0] : 'unknown';
-
- if (!is_primary_key)
- col_type += ' ' + type;
- else
- col_type += ' [PK] ' + type;
-
- if (c.precision && c.precision >= 0 && c.precision != 65535) {
- col_type += ' (' + c.precision;
- col_type += c.scale && c.scale != 65535 ?
- ',' + c.scale + ')':
- ')';
- }
-
- // Identify cell type of column.
- switch(type) {
- case "json":
- case "json[]":
- case "jsonb":
- case "jsonb[]":
- col_cell = 'Json';
- break;
- case "smallint":
- case "integer":
- case "bigint":
- case "decimal":
- case "numeric":
- case "real":
- case "double precision":
- col_cell = 'number';
- break;
- case "boolean":
- col_cell = 'boolean';
- break;
- case "character":
- case "character[]":
- case "character varying":
- case "character varying[]":
- if (c.internal_size && c.internal_size >= 0 && c.internal_size != 65535) {
- // Update column type to display length on column header
- col_type += ' (' + c.internal_size + ')';
- }
- col_cell = 'string';
- break;
- default:
- col_cell = 'string';
- }
-
- column_label = c.display_name + '
' + col_type;
-
- var col = {
- 'name': c.name,
- 'pos': c.pos,
- 'label': column_label,
- 'cell': col_cell,
- 'can_edit': self.can_edit,
- 'type': type,
- 'display_name': c.display_name,
- 'column_type': col_type,
- 'not_null': c.not_null,
- 'has_default_val': c.has_default_val
- };
- columns.push(col);
- });
- }
- else {
- alertify.alert('Fetching Type Error', res.data.result);
- }
- self.columns = columns;
- if (cb && typeof(cb) == 'function') {
- cb();
- }
- },
- fail: _fail
+ // Store pg_types in an array
+ var pg_types = new Array();
+ _.each(data.types, function(r) {
+ pg_types[r.oid] = [r.typname];
});
+
+ // Create columns required by slick grid to render
+ _.each(colinfo, function(c) {
+ var is_primary_key = false;
+
+ // Check whether table have primary key
+ if (_.size(primary_keys) > 0) {
+ _.each(primary_keys, function (value, key) {
+ if (key === c.name)
+ is_primary_key = true;
+ });
+ }
+
+ // To show column label and data type in multiline,
+ // The elements should be put inside the div.
+ // Create column label and type.
+ var col_type = column_label = '';
+ var type = pg_types[c.type_code] ?
+ pg_types[c.type_code][0] :
+ // This is the case where user might
+ // have use casting so we will use type
+ // returned by cast function
+ pg_types[pg_types.length - 1][0] ?
+ pg_types[pg_types.length - 1][0] : 'unknown';
+
+ if (!is_primary_key)
+ col_type += ' ' + type;
+ else
+ col_type += ' [PK] ' + type;
+
+ if (c.precision && c.precision >= 0 && c.precision != 65535) {
+ col_type += ' (' + c.precision;
+ col_type += c.scale && c.scale != 65535 ?
+ ',' + c.scale + ')':
+ ')';
+ }
+
+ // Identify cell type of column.
+ switch(type) {
+ case "json":
+ case "json[]":
+ case "jsonb":
+ case "jsonb[]":
+ col_cell = 'Json';
+ break;
+ case "smallint":
+ case "integer":
+ case "bigint":
+ case "decimal":
+ case "numeric":
+ case "real":
+ case "double precision":
+ col_cell = 'number';
+ break;
+ case "boolean":
+ col_cell = 'boolean';
+ break;
+ case "character":
+ case "character[]":
+ case "character varying":
+ case "character varying[]":
+ if (c.internal_size && c.internal_size >= 0 && c.internal_size != 65535) {
+ // Update column type to display length on column header
+ col_type += ' (' + c.internal_size + ')';
+ }
+ col_cell = 'string';
+ break;
+ default:
+ col_cell = 'string';
+ }
+
+ column_label = c.display_name + '
' + col_type;
+
+ var col = {
+ 'name': c.name,
+ 'display_name': c.display_name,
+ 'column_type': col_type,
+ 'pos': c.pos,
+ 'label': column_label,
+ 'cell': col_cell,
+ 'can_edit': self.can_edit,
+ 'type': type,
+ 'not_null': c.not_null,
+ 'has_default_val': c.has_default_val
+ };
+ columns.push(col);
+ });
+
+ self.columns = columns;
+ if (cb && typeof(cb) == 'function') {
+ cb();
+ }
},
// This function is used to raise appropriate message.
@@ -1982,22 +2034,21 @@ define([
self.gridView.messages_panel.focus();
- if (self.is_query_tool) {
- if (clear_grid) {
- // Delete grid
- if (self.gridView.handler.slickgrid) {
- self.gridView.handler.slickgrid.destroy();
+ if (clear_grid) {
+ // Delete grid
+ if (self.gridView.handler.slickgrid) {
+ self.gridView.handler.slickgrid.destroy();
- }
- // Misc cleaning
- self.columns = undefined;
- self.collection = undefined;
-
- $('.sql-editor-message').text(msg);
- } else {
- $('.sql-editor-message').append(msg);
}
+ // Misc cleaning
+ self.columns = undefined;
+ self.collection = undefined;
+
+ $('.sql-editor-message').text(msg);
+ } else {
+ $('.sql-editor-message').append(msg);
}
+
// Scroll automatically when msgs appends to element
setTimeout(function(){
$(".sql-editor-message").scrollTop($(".sql-editor-message")[0].scrollHeight);;
@@ -2052,7 +2103,7 @@ define([
rows_to_delete: function(data) {
var self = this,
- tmp_keys = self.get_row_primary_key.call(self);
+ tmp_keys = self.primary_keys;
// re-calculate rows with no primary keys
self.temp_new_rows = [];
@@ -2065,7 +2116,6 @@ define([
self.temp_new_rows.push(idx);
}
});
- data.getItemMetadata = self.data_view.getItemMetadata;
self.rows_to_disable = _.clone(self.temp_new_rows);
},
@@ -2076,69 +2126,73 @@ define([
is_added = _.size(self.data_store.added),
is_updated = _.size(self.data_store.updated);
- // Remove newly added rows from staged rows as we don't want to send them on server
- if(is_added) {
- _.each(self.data_store.added, function(val, key) {
- if(key in self.data_store.staged_rows) {
- // Remove the row from data store so that we do not send it on server
- deleted_keys.push(key);
- delete self.data_store.staged_rows[key];
- delete self.data_store.added[key]
- }
- });
+ // Remove newly added rows from staged rows as we don't want to send them on server
+ if(is_added) {
+ _.each(self.data_store.added, function(val, key) {
+ if(key in self.data_store.staged_rows) {
+ // Remove the row from data store so that we do not send it on server
+ deleted_keys.push(key);
+ delete self.data_store.staged_rows[key];
+ delete self.data_store.added[key];
+ delete self.data_store.added_index[key];
}
+ });
+ }
+ // If only newly rows to delete and no data is there to send on server
+ // then just re-render the grid
+ if(_.size(self.data_store.staged_rows) == 0) {
+ var grid = self.slickgrid,
+ dataView = grid.getData(),
+ data = dataView.getItems(),
+ idx = 0;
- // If only newly rows to delete and no data is there to send on server
- // then just re-render the grid
- if(_.size(self.data_store.staged_rows) == 0) {
- var grid = self.slickgrid, data = grid.getData(), idx = 0;
- if(deleted_keys.length){
- // Remove new rows from grid data using deleted keys
- data = _.reject(data, function(d){
- return (d && _.indexOf(deleted_keys, d.__temp_PK) > -1)
- });
- }
- self.rows_to_delete.apply(self, [data]);
- grid.resetActiveCell();
- grid.setData(data, true);
- grid.setSelectedRows([]);
- grid.invalidate();
- // Nothing to copy or delete here
- $("#btn-delete-row").prop('disabled', true);
- $("#btn-copy-row").prop('disabled', true);
- if(_.size(self.data_store.added) || is_updated) {
- // Do not disable save button if there are
- // any other changes present in grid data
- $("#btn-save").prop('disabled', false);
- } else {
- $("#btn-save").prop('disabled', true);
- }
- alertify.success(gettext("Row(s) deleted"));
+ grid.resetActiveCell();
+
+ dataView.beginUpdate();
+ for (var i = 0; i < deleted_keys.length; i++) {
+ dataView.deleteItem(deleted_keys[i]);
+ }
+ dataView.endUpdate();
+ self.rows_to_delete.apply(self, [dataView.getItems()]);
+ grid.resetActiveCell();
+ grid.setSelectedRows([]);
+ grid.invalidate();
+
+ // Nothing to copy or delete here
+ $("#btn-delete-row").prop('disabled', true);
+ $("#btn-copy-row").prop('disabled', true);
+ if(_.size(self.data_store.added) || is_updated) {
+ // Do not disable save button if there are
+ // any other changes present in grid data
+ $("#btn-save").prop('disabled', false);
} else {
- // There are other data to needs to be updated on server
- if(is_updated) {
- alertify.alert(gettext("Operation failed"),
+ $("#btn-save").prop('disabled', true);
+ }
+ alertify.success(gettext("Row(s) deleted"));
+ } else {
+ // There are other data to needs to be updated on server
+ if(is_updated) {
+ alertify.alert(gettext("Operation failed"),
gettext("There are unsaved changes in grid, Please save them first to avoid inconsistency in data")
);
- return;
- }
- alertify.confirm(gettext("Delete Row(s)"),
+ return;
+ }
+ alertify.confirm(gettext("Delete Row(s)"),
gettext("Are you sure you wish to delete selected row(s)?"),
- function() {
- $("#btn-delete-row").prop('disabled', true);
- $("#btn-copy-row").prop('disabled', true);
- // Change the state
- self.data_store.deleted = self.data_store.staged_rows;
- self.data_store.staged_rows = {};
- // Save the changes on server
- self._save();
- },
- function() {
- // Do nothing as user canceled the operation.
- }
- ).set('labels', {ok:'Yes', cancel:'No'});
+ function() {
+ $("#btn-delete-row").prop('disabled', true);
+ $("#btn-copy-row").prop('disabled', true);
+ // Change the state
+ self.data_store.deleted = self.data_store.staged_rows;
+ self.data_store.staged_rows = {};
+ // Save the changes on server
+ self._save();
+ },
+ function() {
+ // Do nothing as user canceled the operation.
}
-
+ ).set('labels', {ok: gettext("Yes"), cancel:gettext("No")});
+ }
},
/* This function will fetch the list of changed models and make
@@ -2200,7 +2254,9 @@ define([
data: JSON.stringify(req_data),
success: function(res) {
var grid = self.slickgrid,
- data = grid.getData();
+ dataView = grid.getData(),
+ data_length = dataView.getLength(),
+ data = [];
if (res.data.status) {
// Remove flag is_row_copied from copied rows
_.each(data, function(row, idx) {
@@ -2217,22 +2273,20 @@ define([
// Remove deleted rows from client as well
if(is_deleted) {
var rows = grid.getSelectedRows();
- /* In JavaScript sorting by default is lexical,
- * To make sorting numerical we need to pass function
- * After that we will Reverse the order of sorted array
- * so that when we remove it does not affect array index
- */
- if(data.length == rows.length) {
+ if(data_length == rows.length) {
// This means all the rows are selected, clear all data
data = [];
+ dataView.setItems(data, self.client_primary_key);
} else {
- rows = rows.sort(function(a,b){return a - b}).reverse();
- rows.forEach(function(idx) {
- data.splice(idx, 1);
- });
+ dataView.beginUpdate();
+ for (var i = 0; i < rows.length; i++) {
+ item = grid.getDataItem(rows[i]);
+ data.push(item);
+ dataView.deleteItem(item[self.client_primary_key]);
+ }
+ dataView.endUpdate();
}
self.rows_to_delete.apply(self, [data]);
- grid.setData(data, true);
grid.setSelectedRows([]);
}
@@ -2245,6 +2299,7 @@ define([
self.rows_to_disable = _.clone(self.temp_new_rows);
}
+ grid.setSelectedRows([]);
// Reset data store
self.data_store = {
'added': {},
@@ -2265,7 +2320,7 @@ define([
$('.sql-editor-message').text(res.data.result);
var err_msg = S(gettext("%s.")).sprintf(res.data.result).value();
alertify.notify(err_msg, 'error', 20);
-
+ grid.setSelectedRows([]);
// To highlight the row at fault
if(_.has(res.data, '_rowid') &&
(!_.isUndefined(res.data._rowid)|| !_.isNull(res.data._rowid))) {
@@ -2321,14 +2376,21 @@ define([
// Find index of row at fault from grid data
_find_rowindex: function(rowid) {
- var self = this;
- var grid = self.slickgrid,
- data = grid.getData(), _rowid, count = 0, _idx = -1;
+ var self = this,
+ grid = self.slickgrid,
+ dataView = grid.getData(),
+ data = dataView.getItems(),
+ _rowid,
+ count = 0,
+ _idx = -1;
+
// If _rowid is object then it's update/delete operation
if(_.isObject(rowid)) {
_rowid = rowid;
- } else if (_.isString(rowid)) { // Insert opration
- _rowid = { '__temp_PK': rowid };
+ } else if (_.isString(rowid)) { // Insert operation
+ var rowid = {};
+ rowid[self.client_primary_key]= rowid;
+ _rowid = rowid;
} else {
// Something is wrong with unique id
return _idx;
@@ -2550,11 +2612,6 @@ define([
// This function will set the required flag for polling response data
_init_polling_flags: function() {
var self = this;
- // Set a flag to get columns
- self.FETCH_COLUMNS_FROM_SERVER = true;
- // We will set columns data in this variable for future use once we fetch it
- // from server
- self.COLUMNS_DATA = {};
// To get a timeout for polling fallback timer in seconds in
// regards to elapsed time
@@ -2650,7 +2707,7 @@ define([
return;
// Add column position and it's value to data
- data[column_info.field] = _values[column_info.pos] || '';
+ data[column_info.field] = _values[column_info.field] || '';
self.trigger(
'pgadmin-sqleditor:loading-icon:show',
@@ -2720,7 +2777,7 @@ define([
return;
// Add column position and it's value to data
- data[column_info.field] = _values[column_info.pos] || '';
+ data[column_info.field] = _values[column_info.field] || '';
self.trigger(
'pgadmin-sqleditor:loading-icon:show',
@@ -2883,16 +2940,18 @@ define([
_paste_row: function() {
var self = this, col_info = {},
grid = self.slickgrid,
- data = grid.getData(),
- count = Object.keys(data).length-1;
-
- var rows = grid.getSelectedRows().sort(
+ dataView = grid.getData(),
+ data = dataView.getItems(),
+ count = dataView.getLength(),
+ rows = grid.getSelectedRows().sort(
function (a, b) { return a - b; }
),
- rows = rows.length == 0 ? self.last_copied_rows : rows,
copied_rows = rows.map(function (rowIndex) {
return data[rowIndex];
});
+
+ rows = rows.length == 0 ? self.last_copied_rows : rows
+
self.last_copied_rows = rows;
// If there are rows to paste?
@@ -2925,22 +2984,22 @@ define([
// Add index of copied row into temp_new_rows
// Trigger grid.onAddNewRow when a row is copied
// Reset selection
+
+ dataView.beginUpdate();
_.each(copied_rows, function(row) {
- var new_row = arr_to_object(row);
+ var new_row = arr_to_object(row),
+ _key = (self.gridView.client_primary_key_counter++).toString();
new_row.is_row_copied = true;
- row = new_row;
self.temp_new_rows.push(count);
- grid.onAddNewRow.notify(
- { item: new_row,
- column: self.columns[0],
- grid: grid,
- is_copy_row: true
- }
- )
+ new_row[self.client_primary_key] = _key;
+ dataView.addItem(new_row);
+ self.data_store.added[_key] = {'err': false, 'data': new_row};
+ self.data_store.added_index[count] = _key;
count++;
});
- // Add a blank row after copy/paste row
- grid.addBlankRow();
+ dataView.endUpdate();
+ grid.updateRowCount();
+ grid.render();
grid.setSelectedRows([]);
}
},
@@ -3012,6 +3071,9 @@ define([
sql = '',
history_msg = '';
+ self.has_more_rows = false;
+ self.fetching_rows = false;
+
/* If code is selected in the code mirror then execute
* the selected part else execute the complete code.
*/
diff --git a/web/pgadmin/utils/driver/abstract.py b/web/pgadmin/utils/driver/abstract.py
index 9b2363c84..7db3e3757 100644
--- a/web/pgadmin/utils/driver/abstract.py
+++ b/web/pgadmin/utils/driver/abstract.py
@@ -101,6 +101,12 @@ class BaseConnection(object):
- Implement this method to execute the given query and returns the result
as an array of dict (column name -> value) format.
+ * def async_fetchmany_2darray(records=-1, formatted_exception_msg=False):
+ - Implement this method to retrieve result of asynchronous connection and
+ polling with no_result flag set to True.
+ This returns the result as a 2 dimensional array.
+ If records is -1 then fetchmany will behave as fetchall.
+
* connected()
- Implement this method to get the status of the connection. It should
return True for connected, otherwise False
@@ -133,7 +139,7 @@ class BaseConnection(object):
- Implement this method to wait for asynchronous connection with timeout.
This must be a non blocking call.
- * poll(formatted_exception_msg)
+ * poll(formatted_exception_msg, no_result)
- Implement this method to poll the data of query running on asynchronous
connection.
@@ -179,6 +185,10 @@ class BaseConnection(object):
def execute_dict(self, query, params=None, formatted_exception_msg=False):
pass
+ @abstractmethod
+ def async_fetchmany_2darray(self, records=-1, formatted_exception_msg=False):
+ pass
+
@abstractmethod
def connected(self):
pass
@@ -208,7 +218,7 @@ class BaseConnection(object):
pass
@abstractmethod
- def poll(self, formatted_exception_msg=True):
+ def poll(self, formatted_exception_msg=True, no_result=False):
pass
@abstractmethod
diff --git a/web/pgadmin/utils/driver/psycopg2/__init__.py b/web/pgadmin/utils/driver/psycopg2/__init__.py
index 16342d46e..8a20521af 100644
--- a/web/pgadmin/utils/driver/psycopg2/__init__.py
+++ b/web/pgadmin/utils/driver/psycopg2/__init__.py
@@ -1079,6 +1079,55 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
return True, {'columns': columns, 'rows': rows}
+ def async_fetchmany_2darray(self, records=2000, formatted_exception_msg=False):
+ """
+ User should poll and check if status is ASYNC_OK before calling this
+ function
+ Args:
+ records: no of records to fetch. use -1 to fetchall.
+ formatted_exception_msg:
+
+ Returns:
+
+ """
+ cur = self.__async_cursor
+ if not cur:
+ return False, gettext(
+ "Cursor could not be found for the async connection."
+ )
+
+ if self.conn.isexecuting():
+ return False, gettext(
+ "Asynchronous query execution/operation underway."
+ )
+
+ if self.row_count > 0:
+ result = []
+ # For DDL operation, we may not have result.
+ #
+ # Because - there is not direct way to differentiate DML and
+ # DDL operations, we need to rely on exception to figure
+ # that out at the moment.
+ try:
+ if records == -1:
+ res = cur.fetchall()
+ else:
+ res = cur.fetchmany(records)
+ for row in res:
+ new_row = []
+ for col in self.column_info:
+ new_row.append(row[col['name']])
+ result.append(new_row)
+ except psycopg2.ProgrammingError as e:
+ result = None
+ else:
+ # User performed operation which dose not produce record/s as
+ # result.
+ # for eg. DDL operations.
+ return True, None
+
+ return True, result
+
def connected(self):
if self.conn:
if not self.conn.closed:
@@ -1226,7 +1275,7 @@ Failed to reset the connection to the server due to following error:
"poll() returned %s from _wait_timeout function" % state
)
- def poll(self, formatted_exception_msg=False):
+ def poll(self, formatted_exception_msg=False, no_result=False):
"""
This function is a wrapper around connection's poll function.
It internally uses the _wait_timeout method to poll the
@@ -1236,6 +1285,7 @@ Failed to reset the connection to the server due to following error:
Args:
formatted_exception_msg: if True then function return the formatted
exception message, otherwise error string.
+ no_result: If True then only poll status will be returned.
"""
cur = self.__async_cursor
@@ -1291,23 +1341,23 @@ Failed to reset the connection to the server due to following error:
pos += 1
self.row_count = cur.rowcount
+ if not no_result:
+ if cur.rowcount > 0:
+ result = []
+ # For DDL operation, we may not have result.
+ #
+ # Because - there is not direct way to differentiate DML and
+ # DDL operations, we need to rely on exception to figure
+ # that out at the moment.
+ try:
+ for row in cur:
+ new_row = []
+ for col in self.column_info:
+ new_row.append(row[col['name']])
+ result.append(new_row)
- if cur.rowcount > 0:
- result = []
- # For DDL operation, we may not have result.
- #
- # Because - there is not direct way to differentiate DML and
- # DDL operations, we need to rely on exception to figure that
- # out at the moment.
- try:
- for row in cur:
- new_row = []
- for col in self.column_info:
- new_row.append(row[col['name']])
- result.append(new_row)
-
- except psycopg2.ProgrammingError:
- result = None
+ except psycopg2.ProgrammingError:
+ result = None
return status, result
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index c61663a33..02c35ed9d 100644
--- a/web/regression/feature_utils/pgadmin_page.py
+++ b/web/regression/feature_utils/pgadmin_page.py
@@ -85,7 +85,7 @@ class PgadminPage:
if 'menu-item' == str(menu_item.get_attribute('class')):
break
- time.sleep(0.1)
+ time.sleep(0.1)
else:
assert False, "'Tools -> Query Tool' menu did not enable."
@@ -144,7 +144,6 @@ class PgadminPage:
except WebDriverException:
return
-
def find_by_xpath(self, xpath):
return self.wait_for_element(lambda driver: driver.find_element_by_xpath(xpath))
@@ -251,6 +250,20 @@ class PgadminPage:
self._wait_for("spinner to disappear", spinner_has_disappeared)
+ def wait_for_query_tool_loading_indicator_to_disappear(self):
+ def spinner_has_disappeared(driver):
+ try:
+ driver.find_element_by_xpath(
+ "//*[@id='fetching_data' and @class='hide']"
+ )
+ return False
+ except NoSuchElementException:
+ # wait for loading indicator disappear animation to complete.
+ time.sleep(0.5)
+ return True
+
+ self._wait_for("spinner to disappear", spinner_has_disappeared)
+
def wait_for_app(self):
def page_shows_app(driver):
if driver.title == self.app_config.APP_NAME:
@@ -266,19 +279,3 @@ class PgadminPage:
timeout = self.timeout
return WebDriverWait(self.driver, timeout, 0.01).until(condition_met_function,
"Timed out waiting for " + waiting_for_message)
-
- def wait_for_element_to_stale(self, xpath):
- # Reference: http://www.obeythetestinggoat.com/
- # how-to-get-selenium-to-wait-for-page-load-after-a-click.html
- el = self.driver.find_element_by_xpath(xpath)
-
- def element_has_gone_stale(driver):
- try:
- # poll an arbitrary element
- el.find_elements_by_id('element-dont-exist')
- return False
- except StaleElementReferenceException:
- return True
-
- self._wait_for("element to attach to the page document",
- element_has_gone_stale)
diff --git a/web/regression/javascript/selection/copy_data_spec.js b/web/regression/javascript/selection/copy_data_spec.js
index 9e05e845f..2b623b38b 100644
--- a/web/regression/javascript/selection/copy_data_spec.js
+++ b/web/regression/javascript/selection/copy_data_spec.js
@@ -16,16 +16,16 @@ import clipboard from '../../../pgadmin/static/js/selection/clipboard';
import copyData from '../../../pgadmin/static/js/selection/copy_data';
import RangeSelectionHelper from 'sources/selection/range_selection_helper';
import XCellSelectionModel from 'sources/selection/xcell_selection_model';
-
describe('copyData', function () {
var grid, sqlEditor, gridContainer, buttonPasteRow;
var SlickGrid;
beforeEach(function () {
SlickGrid = Slick.Grid;
- var data = [[1, 'leopord', '12'],
- [2, 'lion', '13'],
- [3, 'puma', '9']];
+ var data = [{'id': 1, 'brand':'leopord', 'size':'12', '__temp_PK': '123'},
+ {'id': 2, 'brand':'lion', 'size':'13', '__temp_PK': '456'},
+ {'id': 3, 'brand':'puma', 'size':'9', '__temp_PK': '789'}],
+ dataView = new Slick.Data.DataView();
var columns = [
{
@@ -37,6 +37,7 @@ describe('copyData', function () {
},
{
name: 'id',
+ field: 'id',
pos: 0,
label: 'id
numeric',
cell: 'number',
@@ -44,6 +45,7 @@ describe('copyData', function () {
type: 'numeric',
}, {
name: 'brand',
+ field: 'brand',
pos: 1,
label: 'flavor
character varying',
cell: 'string',
@@ -51,24 +53,26 @@ describe('copyData', function () {
type: 'character varying',
}, {
name: 'size',
+ field: 'size',
pos: 2,
label: 'size
numeric',
cell: 'number',
can_edit: false,
type: 'numeric',
},
- ]
- ;
- gridContainer = $('');
+ ];
+ gridContainer = $('');
$('body').append(gridContainer);
- buttonPasteRow = $('');
+ buttonPasteRow = $('');
$('body').append(buttonPasteRow);
- grid = new SlickGrid('#grid', data, columns, {});
+ grid = new SlickGrid('#grid', dataView, columns, {});
+ dataView.setItems(data, '__temp_PK');
grid.setSelectionModel(new XCellSelectionModel());
sqlEditor = {slickgrid: grid};
});
- afterEach(function () {
+ afterEach(function() {
+ grid.destroy();
gridContainer.remove();
buttonPasteRow.remove();
});
diff --git a/web/regression/javascript/selection/range_boundary_navigator_spec.js b/web/regression/javascript/selection/range_boundary_navigator_spec.js
index 1de6d8124..2d56b056b 100644
--- a/web/regression/javascript/selection/range_boundary_navigator_spec.js
+++ b/web/regression/javascript/selection/range_boundary_navigator_spec.js
@@ -134,17 +134,19 @@ describe('RangeBoundaryNavigator', function () {
describe('#rangesToCsv', function () {
var data, columnDefinitions, ranges;
beforeEach(function () {
- data = [[1, 'leopard', '12'],
- [2, 'lion', '13'],
- [3, 'cougar', '9'],
- [4, 'tiger', '10']];
- columnDefinitions = [{name: 'id', pos: 0}, {name: 'animal', pos: 1}, {name: 'size', pos: 2}];
+ data = [{'id':1, 'animal':'leopard', 'size':'12'},
+ {'id':2, 'animal':'lion', 'size':'13'},
+ {'id':3, 'animal':'cougar', 'size':'9'},
+ {'id':4, 'animal':'tiger', 'size':'10'}];
+
+ columnDefinitions = [{name: 'id', field: 'id', pos: 0},
+ {name: 'animal', field: 'animal', pos: 1},
+ {name: 'size', field: 'size', pos: 2}];
ranges = [new Slick.Range(0, 0, 0, 2), new Slick.Range(3, 0, 3, 2)];
});
it('returns csv for the provided ranges', function () {
var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges);
-
expect(csvResult).toEqual('1,\'leopard\',\'12\'\n4,\'tiger\',\'10\'');
});
@@ -158,10 +160,10 @@ describe('RangeBoundaryNavigator', function () {
describe('when there is an extra column with checkboxes', function () {
beforeEach(function () {
- columnDefinitions = [{name: 'not-a-data-column'}, {name: 'id', pos: 0}, {name: 'animal', pos: 1}, {
- name: 'size',
- pos: 2,
- }];
+ columnDefinitions = [{name: 'not-a-data-column'},
+ {name: 'id', field: 'id', pos: 0},
+ {name: 'animal', field: 'animal', pos: 1},
+ {name: 'size', field: 'size',pos: 2}];
ranges = [new Slick.Range(0, 0, 0, 3), new Slick.Range(3, 0, 3, 3)];
});
diff --git a/web/regression/javascript/selection/row_selector_spec.js b/web/regression/javascript/selection/row_selector_spec.js
index db3c0b4f2..b6d4d0120 100644
--- a/web/regression/javascript/selection/row_selector_spec.js
+++ b/web/regression/javascript/selection/row_selector_spec.js
@@ -6,7 +6,6 @@
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
-
import $ from 'jquery';
import Slick from 'slickgrid';
@@ -23,7 +22,7 @@ describe('RowSelector', function () {
UP: 38,
DOWN: 40,
};
- var container, data, columnDefinitions, grid, cellSelectionModel;
+ var container, dataView, columnDefinitions, grid, cellSelectionModel;
var SlickGrid = Slick.Grid;
beforeEach(function () {
@@ -43,14 +42,15 @@ describe('RowSelector', function () {
pos: 1,
}];
+ dataView = new Slick.Data.DataView();
var rowSelector = new RowSelector();
- data = [];
+ var data = [];
for (var i = 0; i < 10; i++) {
- data.push(['some-value-' + i, 'second value ' + i]);
+ data.push({'some-column-name':'some-value-' + i, 'second column':'second value ' + i});
}
columnDefinitions = rowSelector.getColumnDefinitions(columnDefinitions);
- grid = new SlickGrid(container, data, columnDefinitions);
-
+ dataView.setItems(data, 'some-column-name');
+ grid = new SlickGrid(container, dataView, columnDefinitions);
grid.registerPlugin(new ActiveCellCapture());
cellSelectionModel = new XCellSelectionModel();
grid.setSelectionModel(cellSelectionModel);
diff --git a/web/regression/javascript/selection/set_staged_rows_spec.js b/web/regression/javascript/selection/set_staged_rows_spec.js
index 673531446..6fd780a5a 100644
--- a/web/regression/javascript/selection/set_staged_rows_spec.js
+++ b/web/regression/javascript/selection/set_staged_rows_spec.js
@@ -7,236 +7,247 @@
//
//////////////////////////////////////////////////////////////
-define([
- 'jquery',
- 'underscore',
- 'sources/selection/set_staged_rows',
-], function ($, _, SetStagedRows) {
- describe('set_staged_rows', function () {
- var sqlEditorObj, gridSpy, deleteButton, copyButton, selectionSpy;
- beforeEach(function () {
- gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode', 'getColumns']);
- gridSpy.getData.and.returnValue([
- {0: 'one', 1: 'two', __temp_PK: '123'},
- {0: 'three', 1: 'four', __temp_PK: '456'},
- {0: 'five', 1: 'six', __temp_PK: '789'},
- {0: 'seven', 1: 'eight', __temp_PK: '432'},
- ]);
- gridSpy.getColumns.and.returnValue([
+import $ from 'jquery';
+import 'slickgrid.grid';
+import Slick from 'slickgrid';
+import SetStagedRows from 'sources/selection/set_staged_rows';
+
+describe('set_staged_rows', function () {
+ var sqlEditorObj, gridSpy, deleteButton, copyButton, selectionSpy;
+ beforeEach(function () {
+ var data = [{'a pk column': 'one', 'some column': 'two', '__temp_PK': '123'},
+ {'a pk column': 'three', 'some column': 'four', '__temp_PK': '456'},
+ {'a pk column': 'five', 'some column': 'six', '__temp_PK': '789'},
+ {'a pk column': 'seven', 'some column': 'eight', '__temp_PK': '432'}],
+ dataView = new Slick.Data.DataView();
+
+ dataView.setItems(data, '__temp_PK');
+
+ gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode', 'getColumns']);
+ gridSpy.getData.and.returnValue(dataView);
+ gridSpy.getColumns.and.returnValue([
+ {
+ name: 'a pk column',
+ field: 'a pk column',
+ pos: 0,
+ selectable: true,
+ }, {
+ name: 'some column',
+ field: 'some column',
+ pos: 1,
+ selectable: true,
+ },
+ ]);
+ selectionSpy = jasmine.createSpyObj('selectionSpy', ['setSelectedRows', 'getSelectedRanges']);
+ deleteButton = $('');
+ copyButton = $('');
+
+ sqlEditorObj = {
+ grid: gridSpy,
+ editor: {
+ handler: {
+ data_store: {
+ staged_rows: {'456': {}},
+ },
+ can_edit: false,
+ },
+ },
+ keys: null,
+ selection: selectionSpy,
+ columns: [
{
+ name: 'a pk column',
+ field: 'a pk column',
pos: 0,
- selectable: true,
- }, {
+ },
+ {
+ name: 'some column',
+ field: 'some column',
pos: 1,
- selectable: true,
},
- ]);
+ ],
+ client_primary_key: '__temp_PK',
+ };
- selectionSpy = jasmine.createSpyObj('selectionSpy', ['setSelectedRows', 'getSelectedRanges']);
+ $('body').append(deleteButton);
+ $('body').append(copyButton);
- deleteButton = $('');
- copyButton = $('');
+ deleteButton.prop('disabled', true);
+ copyButton.prop('disabled', true);
- sqlEditorObj = {
- grid: gridSpy,
- editor: {
- handler: {
- data_store: {
- staged_rows: {'456': {}},
- },
- can_edit: false,
- },
- },
- keys: null,
- selection: selectionSpy,
- columns: [
- {
- name: 'a pk column',
- pos: 0,
- },
- {
- name: 'some column',
- pos: 1,
- },
- ],
- };
+ selectionSpy = jasmine.createSpyObj('selectionSpy', [
+ 'setSelectedRows',
+ 'getSelectedRanges',
+ ]);
+ });
- $('body').append(deleteButton);
- $('body').append(copyButton);
-
- deleteButton.prop('disabled', true);
- copyButton.prop('disabled', true);
-
- selectionSpy = jasmine.createSpyObj('selectionSpy', [
- 'setSelectedRows',
- 'getSelectedRanges',
- ]);
- });
-
- afterEach(function () {
- copyButton.remove();
- deleteButton.remove();
- });
- describe('when no full rows are selected', function () {
- describe('when nothing is selected', function () {
- beforeEach(function () {
- selectionSpy.getSelectedRanges.and.returnValue([]);
- sqlEditorObj.selection = selectionSpy;
- SetStagedRows.call(sqlEditorObj, {}, {});
- });
-
- it('should disable the delete row button', function () {
- expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
- });
-
- it('should disable the copy row button', function () {
- expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
- });
-
- it('should clear staged rows', function () {
- expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
- });
- });
-
- describe('when there is a selection', function () {
- beforeEach(function () {
- var range = {
- fromCell: 0,
- toCell: 0,
- fromRow: 1,
- toRow: 1,
- };
-
- selectionSpy.getSelectedRanges.and.returnValue([range]);
- sqlEditorObj.selection = selectionSpy;
- SetStagedRows.call(sqlEditorObj, {}, {});
- });
-
- it('should disable the delete row button', function () {
- expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
- });
-
- it('should disable the copy row button', function () {
- expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
- });
-
- it('should clear staged rows', function () {
- expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
- });
- });
- });
-
- describe('when 2 full rows are selected', function () {
+ afterEach(function () {
+ copyButton.remove();
+ deleteButton.remove();
+ });
+ describe('when no full rows are selected', function () {
+ describe('when nothing is selected', function () {
beforeEach(function () {
- var range1 = {
+ selectionSpy.getSelectedRanges.and.returnValue([]);
+ sqlEditorObj.selection = selectionSpy;
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ });
+
+ it('should disable the delete row button', function () {
+ expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
+ });
+
+ it('should disable the copy row button', function () {
+ expect($('#btn-copy-row').prop('disabled')).toBeTruthy();
+ });
+
+ it('should clear staged rows', function () {
+ expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
+ });
+ });
+
+ describe('when there is a selection', function () {
+ beforeEach(function () {
+ var range = {
fromCell: 0,
- toCell: 1,
+ toCell: 0,
fromRow: 1,
toRow: 1,
};
- var range2 = {
- fromCell: 0,
- toCell: 1,
- fromRow: 2,
- toRow: 2,
- };
- selectionSpy.getSelectedRanges.and.returnValue([range1, range2]);
+ selectionSpy.getSelectedRanges.and.returnValue([range]);
sqlEditorObj.selection = selectionSpy;
+ SetStagedRows.call(sqlEditorObj, {}, {});
});
- describe('when table does not have primary keys', function () {
- it('should enable the copy row button', function () {
+ it('should disable the delete row button', function () {
+ expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
+ });
+
+ it('should disable the copy row button', function () {
+ expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
+ });
+
+ it('should clear staged rows', function () {
+ expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
+ });
+ });
+ });
+
+ describe('when 2 full rows are selected', function () {
+ beforeEach(function () {
+ var range1 = {
+ fromCell: 0,
+ toCell: 1,
+ fromRow: 1,
+ toRow: 1,
+ };
+ var range2 = {
+ fromCell: 0,
+ toCell: 1,
+ fromRow: 2,
+ toRow: 2,
+ };
+
+ selectionSpy.getSelectedRanges.and.returnValue([range1, range2]);
+ sqlEditorObj.selection = selectionSpy;
+ });
+
+ describe('when table does not have primary keys', function () {
+ it('should enable the copy row button', function () {
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
+ });
+
+ it('should not enable the delete row button', function () {
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
+ });
+
+ it('should update staged rows with the __temp_PK value of the new Selected Rows', function () {
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({'456': {}, '789': {}});
+ });
+
+ describe('the user can edit', function () {
+ it('should enable the delete row button', function () {
+ sqlEditorObj.editor.handler.can_edit = true;
SetStagedRows.call(sqlEditorObj, {}, {});
- expect($('#btn-copy-row').prop('disabled')).toBeFalsy();
+ expect($('#btn-delete-row').prop('disabled')).toBeFalsy();
+ });
+ });
+ });
+
+ describe('when table has primary keys', function () {
+ beforeEach(function () {
+ sqlEditorObj.keys = {'a pk column': 'varchar'};
+ sqlEditorObj.editor.handler.data_store.staged_rows = {'456': {'a pk column': 'three'}};
+ });
+
+ describe('selected rows have primary key', function () {
+ it('should set the staged rows correctly', function () {
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual(
+ {'456': {'a pk column': 'three'}, '789': {'a pk column': 'five'}});
});
- it('should not enable the delete row button', function () {
+ it('should not clear selected rows in Cell Selection Model', function () {
SetStagedRows.call(sqlEditorObj, {}, {});
- expect($('#btn-delete-row').prop('disabled')).toBeTruthy();
- });
-
- it('should update staged rows with the __temp_PK value of the new Selected Rows', function () {
- SetStagedRows.call(sqlEditorObj, {}, {});
- expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({'456': {}, '789': {}});
- });
-
- describe('the user can edit', function () {
- it('should enable the delete row button', function () {
- sqlEditorObj.editor.handler.can_edit = true;
- SetStagedRows.call(sqlEditorObj, {}, {});
- expect($('#btn-delete-row').prop('disabled')).toBeFalsy();
- });
+ expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith();
});
});
- describe('when table has primary keys', function () {
+ describe('selected rows missing primary key', function () {
beforeEach(function () {
- sqlEditorObj.keys = {'a pk column': 'varchar'};
- sqlEditorObj.editor.handler.data_store.staged_rows = {'456': {0: 'three'}};
+ var data = [{'a pk column': 'one', 'some column': 'two', '__temp_PK': '123'},
+ {'some column': 'four', '__temp_PK': '456'},
+ {'some column': 'six', '__temp_PK': '789'},
+ {'a pk column': 'seven', 'some column': 'eight', '__temp_PK': '432'}],
+ dataView = new Slick.Data.DataView();
+
+ dataView.setItems(data, '__temp_PK');
+
+ gridSpy.getData.and.returnValue(dataView);
});
- describe('selected rows have primary key', function () {
- it('should set the staged rows correctly', function () {
- SetStagedRows.call(sqlEditorObj, {}, {});
- expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual(
- {'456': {0: 'three'}, '789': {0: 'five'}});
- });
+ it('should clear the staged rows', function () {
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
+ });
- it('should not clear selected rows in Cell Selection Model', function () {
- SetStagedRows.call(sqlEditorObj, {}, {});
- expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith();
+ it('should clear selected rows in Cell Selection Model', function () {
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]);
+ });
+ });
+
+ describe('when the selected row is a new row', function () {
+ var parentDiv;
+ beforeEach(function () {
+ var childDiv = $('');
+ parentDiv = $('');
+ parentDiv.append(childDiv);
+ $('body').append(parentDiv);
+ gridSpy.getCellNode.and.returnValue(childDiv);
+ SetStagedRows.call(sqlEditorObj, {}, {});
+ });
+
+ afterEach(function () {
+ parentDiv.remove();
+ });
+
+ it('should not clear the staged rows', function () {
+ expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({
+ '456': {'a pk column': 'three'},
+ '789': {'a pk column': 'five'},
});
});
- describe('selected rows missing primary key', function () {
- beforeEach(function () {
- gridSpy.getData.and.returnValue([
- {0: 'one', 1: 'two', __temp_PK: '123'},
- {1: 'four', __temp_PK: '456'},
- {1: 'six', __temp_PK: '789'},
- {0: 'seven', 1: 'eight', __temp_PK: '432'},
- ]);
- });
-
- it('should clear the staged rows', function () {
- SetStagedRows.call(sqlEditorObj, {}, {});
- expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({});
- });
-
- it('should clear selected rows in Cell Selection Model', function () {
- SetStagedRows.call(sqlEditorObj, {}, {});
- expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]);
- });
- });
-
- describe('when the selected row is a new row', function () {
- var parentDiv;
- beforeEach(function () {
- var childDiv = $('');
- parentDiv = $('');
- parentDiv.append(childDiv);
- $('body').append(parentDiv);
- gridSpy.getCellNode.and.returnValue(childDiv);
- SetStagedRows.call(sqlEditorObj, {}, {});
- });
-
- afterEach(function () {
- parentDiv.remove();
- });
-
- it('should not clear the staged rows', function () {
- expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({
- '456': {0: 'three'},
- '789': {0: 'five'},
- });
- });
-
- it('should not clear selected rows in Cell Selection Model', function () {
- expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalled();
- });
+ it('should not clear selected rows in Cell Selection Model', function () {
+ expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalled();
});
});
});
});
});
+
diff --git a/web/regression/javascript/selection/xcell_selection_model_spec.js b/web/regression/javascript/selection/xcell_selection_model_spec.js
index f41d96c4f..a7f19aa06 100644
--- a/web/regression/javascript/selection/xcell_selection_model_spec.js
+++ b/web/regression/javascript/selection/xcell_selection_model_spec.js
@@ -33,14 +33,17 @@ describe('XCellSelectionModel', function () {
}, {
id: '1',
name: 'some-column-name',
+ field: 'some-column-name',
pos: 0,
}, {
id: 'second-column-id',
name: 'second column',
+ field: 'second column',
pos: 1,
}, {
id: 'third-column-id',
name: 'third column',
+ field: 'third column',
pos: 2,
},
];
@@ -52,13 +55,15 @@ describe('XCellSelectionModel', function () {
'second column': 'second value ' + i,
'third column': 'third value ' + i,
'fourth column': 'fourth value ' + i,
+ '__temp_PK': '123' + i,
});
}
container = $('');
+ var dataView = new Slick.Data.DataView();
container.height(9999);
container.width(9999);
-
- grid = new SlickGrid(container, data, columns);
+ dataView.setItems(data, '__temp_PK');
+ grid = new SlickGrid(container, dataView, columns);
grid.setSelectionModel(new XCellSelectionModel());
$('body').append(container);
});
diff --git a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
index e2edaf784..807e468e0 100644
--- a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
+++ b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
@@ -28,21 +28,22 @@ describe('#handleQueryOutputKeyboardEvent', function () {
metaKey: false,
which: -1,
keyCode: -1,
- preventDefault: jasmine.createSpy('preventDefault'),
+ preventDefault: jasmine.createSpy('preventDefault')
};
- var data = [['', '0,0-cell-content', '0,1-cell-content'],
- ['', '1,0-cell-content', '1,1-cell-content'],
- ['', '2,0-cell-content', '2,1-cell-content']];
- var columnDefinitions = [{name: 'checkboxColumn'}, {pos: 1, name: 'firstColumn'}, {
- pos: 2,
- name: 'secondColumn',
- }];
- grid = new SlickGrid($(''), data, columnDefinitions);
- grid.setSelectionModel(new XCellSelectionModel());
+ var data = [{'checkboxColumn': '', 'firstColumn': '0,0-cell-content', 'secondColumn': '0,1-cell-content', '__temp_PK': '123'},
+ {'checkboxColumn': '', 'firstColumn': '1,0-cell-content', 'secondColumn': '1,1-cell-content', '__temp_PK': '456'},
+ {'checkboxColumn': '', 'firstColumn': '2,0-cell-content', 'secondColumn': '2,1-cell-content', '__temp_PK': '789'}],
+ columnDefinitions = [{name: 'checkboxColumn'},
+ {pos: 1, name: 'firstColumn', field: 'firstColumn'},
+ { pos: 2, name: 'secondColumn', field: 'secondColumn'}],
+ dataView = new Slick.Data.DataView();
+ grid = new Slick.Grid($(''), dataView, columnDefinitions);
+ grid.setSelectionModel(new XCellSelectionModel());
+ dataView.setItems(data, '__temp_PK');
slickEvent = {
- grid: grid,
+ grid: grid
};
spyOn(clipboard, 'copyTextToClipboard');