mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-09 23:15:58 -06:00
Use on-demand loading for results in the query tool. Fixes #2137
With a 27420 row query, pgAdmin III runs the query in 5.873s on my laptop. pgAdmin 4 now takes ~1s.
This commit is contained in:
parent
15cb9fc35b
commit
c65158312d
@ -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
|
||||
##########################################################################
|
||||
|
@ -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')
|
||||
|
@ -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,
|
||||
|
734
web/pgadmin/feature_tests/query_tool_tests.py
Normal file
734
web/pgadmin/feature_tests/query_tool_tests.py
Normal file
@ -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")]'
|
||||
)
|
@ -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:
|
||||
|
@ -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 '<img src=\"x\" onerror=\"console.log(1)\">'")
|
||||
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,
|
||||
|
@ -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")) \
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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) + '\'';
|
||||
|
@ -82,7 +82,8 @@ define([
|
||||
formatter: function (rowIndex) {
|
||||
return '<span ' +
|
||||
'data-row="' + rowIndex + '" ' +
|
||||
'data-cell-type="row-header-selector"/>';
|
||||
'data-cell-type="row-header-selector">' +
|
||||
(rowIndex+1) + '</span>';
|
||||
},
|
||||
width: 30,
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
);
|
@ -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();
|
||||
|
@ -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/<int:trans_id>', 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/<int:trans_id>', 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/<int:trans_id>', methods=["GET"], endpoint='fetch_types'
|
||||
)
|
||||
@blueprint.route('/fetch/<int:trans_id>', methods=["GET"], endpoint='fetch')
|
||||
@blueprint.route('/fetch/<int:trans_id>/<int:fetch_all>', 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
|
||||
|
@ -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):
|
||||
|
@ -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 {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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<br> numeric',
|
||||
cell: 'number',
|
||||
@ -44,6 +45,7 @@ describe('copyData', function () {
|
||||
type: 'numeric',
|
||||
}, {
|
||||
name: 'brand',
|
||||
field: 'brand',
|
||||
pos: 1,
|
||||
label: 'flavor<br> character varying',
|
||||
cell: 'string',
|
||||
@ -51,24 +53,26 @@ describe('copyData', function () {
|
||||
type: 'character varying',
|
||||
}, {
|
||||
name: 'size',
|
||||
field: 'size',
|
||||
pos: 2,
|
||||
label: 'size<br> numeric',
|
||||
cell: 'number',
|
||||
can_edit: false,
|
||||
type: 'numeric',
|
||||
},
|
||||
]
|
||||
;
|
||||
gridContainer = $('<div id=\'grid\'></div>');
|
||||
];
|
||||
gridContainer = $('<div id="grid"></div>');
|
||||
$('body').append(gridContainer);
|
||||
buttonPasteRow = $('<button id=\'btn-paste-row\' disabled></button>');
|
||||
buttonPasteRow = $('<button id="btn-paste-row" disabled></button>');
|
||||
$('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();
|
||||
});
|
||||
|
@ -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)];
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 = $('<button id="btn-delete-row"></button>');
|
||||
copyButton = $('<button id="btn-copy-row"></button>');
|
||||
|
||||
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 = $('<button id="btn-delete-row"></button>');
|
||||
copyButton = $('<button id="btn-copy-row"></button>');
|
||||
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 = $('<div></div>');
|
||||
parentDiv = $('<div class="new_row"></div>');
|
||||
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 = $('<div></div>');
|
||||
parentDiv = $('<div class="new_row"></div>');
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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 = $('<div></div>');
|
||||
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);
|
||||
});
|
||||
|
@ -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($('<div></div>'), 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($('<div></div>'), dataView, columnDefinitions);
|
||||
grid.setSelectionModel(new XCellSelectionModel());
|
||||
dataView.setItems(data, '__temp_PK');
|
||||
slickEvent = {
|
||||
grid: grid,
|
||||
grid: grid
|
||||
};
|
||||
|
||||
spyOn(clipboard, 'copyTextToClipboard');
|
||||
|
Loading…
Reference in New Issue
Block a user