This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push:
new 177bed3 allow cache and force refresh on table list (#6078)
177bed3 is described below
commit 177bed3bb6cc2ee1d25d20915b5f006bd8c5c305
Author: Junda Yang <[email protected]>
AuthorDate: Tue Oct 16 13:14:45 2018 -0700
allow cache and force refresh on table list (#6078)
* allow cache and force refresh on table list
* wording
* flake8
* javascript test
* address comments
* nit
---
.../javascripts/sqllab/SqlEditorLeftBar_spec.jsx | 10 +--
.../src/SqlLab/components/SqlEditorLeftBar.jsx | 77 ++++++++++++----------
superset/cache_util.py | 31 ++++-----
superset/db_engine_specs.py | 34 ++++++----
superset/models/core.py | 26 +++++++-
superset/views/core.py | 13 ++--
6 files changed, 112 insertions(+), 79 deletions(-)
diff --git a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
index b5f0187..08fa24a 100644
--- a/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/SqlEditorLeftBar_spec.jsx
@@ -41,14 +41,10 @@ describe('SqlEditorLeftBar', () => {
expect(wrapper.find(TableElement)).toHaveLength(1);
});
describe('onDatabaseChange', () => {
- it('should fetch tables', () => {
- sinon.stub(wrapper.instance(), 'fetchTables');
+ it('should fetch schemas', () => {
sinon.stub(wrapper.instance(), 'fetchSchemas');
wrapper.instance().onDatabaseChange({ value: 1, label: 'main' });
-
- expect(wrapper.instance().fetchTables.getCall(0).args[0]).toBe(1);
expect(wrapper.instance().fetchSchemas.getCall(0).args[0]).toBe(1);
- wrapper.instance().fetchTables.restore();
wrapper.instance().fetchSchemas.restore();
});
it('should clear tableOptions', () => {
@@ -103,9 +99,9 @@ describe('SqlEditorLeftBar', () => {
d.resolve(tables);
return d.promise();
});
- wrapper.instance().fetchTables(1, 'main', 'birth_names');
+ wrapper.instance().fetchTables(1, 'main', 'true', 'birth_names');
-
expect(ajaxStub.getCall(0).args[0]).toBe('/superset/tables/1/main/birth_names/');
+
expect(ajaxStub.getCall(0).args[0]).toBe('/superset/tables/1/main/birth_names/true/');
expect(wrapper.state().tableLength).toBe(3);
});
it('should handle error', () => {
diff --git a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
index d8db05c..e6e7f5d 100644
--- a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
+++ b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
@@ -40,13 +40,10 @@ class SqlEditorLeftBar extends React.PureComponent {
}
onDatabaseChange(db, force) {
const val = db ? db.value : null;
- this.setState({ schemaOptions: [] });
+ this.setState({ schemaOptions: [], tableOptions: [] });
this.props.actions.queryEditorSetSchema(this.props.queryEditor, null);
this.props.actions.queryEditorSetDb(this.props.queryEditor, val);
- if (!(db)) {
- this.setState({ tableOptions: [] });
- } else {
- this.fetchTables(val, this.props.queryEditor.schema);
+ if (db) {
this.fetchSchemas(val, force || false);
}
}
@@ -69,11 +66,12 @@ class SqlEditorLeftBar extends React.PureComponent {
resetState() {
this.props.actions.resetState();
}
- fetchTables(dbId, schema, substr) {
+ fetchTables(dbId, schema, force, substr) {
// This can be large so it shouldn't be put in the Redux store
+ const forceRefresh = force || false;
if (dbId && schema) {
this.setState({ tableLoading: true, tableOptions: [] });
- const url = `/superset/tables/${dbId}/${schema}/${substr}/`;
+ const url =
`/superset/tables/${dbId}/${schema}/${substr}/${forceRefresh}/`;
$.get(url).done((data) => {
const filterOptions = createFilterOptions({ options: data.options });
this.setState({
@@ -110,10 +108,10 @@ class SqlEditorLeftBar extends React.PureComponent {
}
this.props.actions.addTable(this.props.queryEditor, tableName, schemaName);
}
- changeSchema(schemaOpt) {
+ changeSchema(schemaOpt, force) {
const schema = (schemaOpt) ? schemaOpt.value : null;
this.props.actions.queryEditorSetSchema(this.props.queryEditor, schema);
- this.fetchTables(this.props.queryEditor.dbId, schema);
+ this.fetchTables(this.props.queryEditor.dbId, schema, force);
}
fetchSchemas(dbId, force) {
const actualDbId = dbId || this.props.queryEditor.dbId;
@@ -196,7 +194,7 @@ class SqlEditorLeftBar extends React.PureComponent {
<RefreshLabel
onClick={this.onDatabaseChange.bind(
this, { value: database.id }, true)}
- tooltipContent="force refresh schema list"
+ tooltipContent={t('force refresh schema list')}
/>
</div>
</div>
@@ -214,30 +212,41 @@ class SqlEditorLeftBar extends React.PureComponent {
</i>)
</small>
</ControlLabel>
- {this.props.queryEditor.schema &&
- <Select
- name="select-table"
- ref="selectTable"
- isLoading={this.state.tableLoading}
- placeholder={t('Select table or type table name')}
- autosize={false}
- onChange={this.changeTable.bind(this)}
- filterOptions={this.state.filterOptions}
- options={this.state.tableOptions}
- />
- }
- {!this.props.queryEditor.schema &&
- <Select
- async
- name="async-select-table"
- ref="selectTable"
- placeholder={tableSelectPlaceholder}
- disabled={tableSelectDisabled}
- autosize={false}
- onChange={this.changeTable.bind(this)}
- loadOptions={this.getTableNamesBySubStr.bind(this)}
- />
- }
+ <div className="row">
+ <div className="col-md-11 col-xs-11" style={{ paddingRight: '2px'
}}>
+ {this.props.queryEditor.schema &&
+ <Select
+ name="select-table"
+ ref="selectTable"
+ isLoading={this.state.tableLoading}
+ placeholder={t('Select table or type table name')}
+ autosize={false}
+ onChange={this.changeTable.bind(this)}
+ filterOptions={this.state.filterOptions}
+ options={this.state.tableOptions}
+ />
+ }
+ {!this.props.queryEditor.schema &&
+ <Select
+ async
+ name="async-select-table"
+ ref="selectTable"
+ placeholder={tableSelectPlaceholder}
+ disabled={tableSelectDisabled}
+ autosize={false}
+ onChange={this.changeTable.bind(this)}
+ loadOptions={this.getTableNamesBySubStr.bind(this)}
+ />
+ }
+ </div>
+ <div className="col-md-1 col-xs-1" style={{ paddingTop: '8px',
paddingLeft: '0px' }}>
+ <RefreshLabel
+ onClick={this.changeSchema.bind(
+ this, { value: this.props.queryEditor.schema }, true)}
+ tooltipContent={t('force refresh table list')}
+ />
+ </div>
+ </div>
</div>
<hr />
<div className="m-t-5">
diff --git a/superset/cache_util.py b/superset/cache_util.py
index 12bed2b..843ffdb 100644
--- a/superset/cache_util.py
+++ b/superset/cache_util.py
@@ -9,25 +9,17 @@ def view_cache_key(*unused_args, **unused_kwargs):
return 'view/{}/{}'.format(request.path, args_hash)
-def default_timeout(*unused_args, **unused_kwargs):
- return 5 * 60
-
-
-def default_enable_cache(*unused_args, **unused_kwargs):
- return True
+def memoized_func(key=view_cache_key, use_tables_cache=False):
+ """Use this decorator to cache functions that have predefined first arg.
+ enable_cache is treated as True by default,
+ except enable_cache = False is passed to the decorated function.
-def memoized_func(timeout=default_timeout,
- key=view_cache_key,
- enable_cache=default_enable_cache,
- use_tables_cache=False):
- """Use this decorator to cache functions that have predefined first arg.
+ force means whether to force refresh the cache and is treated as False by
default,
+ except force = True is passed to the decorated function.
- If enable_cache() is False,
- the function will never be cached.
- If enable_cache() is True,
- cache is adopted and will timeout in timeout() seconds.
- If force is True, cache will be refreshed.
+ timeout of cache is set to 600 seconds by default,
+ except cache_timeout = {timeout in seconds} is passed to the decorated
function.
memoized_func uses simple_cache and stored the data in memory.
Key is a callable function that takes function arguments and
@@ -42,15 +34,16 @@ def memoized_func(timeout=default_timeout,
if selected_cache:
def wrapped_f(cls, *args, **kwargs):
- if not enable_cache(*args, **kwargs):
+ if not kwargs.get('enable_cache', True):
return f(cls, *args, **kwargs)
cache_key = key(*args, **kwargs)
o = selected_cache.get(cache_key)
- if not kwargs['force'] and o is not None:
+ if not kwargs.get('force') and o is not None:
return o
o = f(cls, *args, **kwargs)
- selected_cache.set(cache_key, o, timeout=timeout(*args,
**kwargs))
+ selected_cache.set(cache_key, o,
+ timeout=kwargs.get('cache_timeout', 600))
return o
else:
# noop
diff --git a/superset/db_engine_specs.py b/superset/db_engine_specs.py
index 172cf9c..20d57d6 100644
--- a/superset/db_engine_specs.py
+++ b/superset/db_engine_specs.py
@@ -228,7 +228,6 @@ class BaseEngineSpec(object):
@classmethod
@cache_util.memoized_func(
- timeout=600,
key=lambda *args, **kwargs: 'db:{}:{}'.format(args[0].id, args[1]),
use_tables_cache=True)
def fetch_result_sets(cls, db, datasource_type, force=False):
@@ -295,8 +294,6 @@ class BaseEngineSpec(object):
@classmethod
@cache_util.memoized_func(
- enable_cache=lambda *args, **kwargs: kwargs.get('enable_cache', False),
- timeout=lambda *args, **kwargs: kwargs.get('cache_timeout'),
key=lambda *args, **kwargs:
'db:{}:schema_list'.format(kwargs.get('db_id')))
def get_schema_names(cls, inspector, db_id,
enable_cache, cache_timeout, force=False):
@@ -312,10 +309,22 @@ class BaseEngineSpec(object):
return inspector.get_schema_names()
@classmethod
- def get_table_names(cls, schema, inspector):
+ @cache_util.memoized_func(
+ key=lambda *args, **kwargs:
'db:{db_id}:schema:{schema}:table_list'.format(
+ db_id=kwargs.get('db_id'), schema=kwargs.get('schema')))
+ def get_table_names(cls, inspector, db_id, schema,
+ enable_cache, cache_timeout, force=False):
return sorted(inspector.get_table_names(schema))
@classmethod
+ @cache_util.memoized_func(
+ key=lambda *args, **kwargs:
'db:{db_id}:schema:{schema}:view_list'.format(
+ db_id=kwargs.get('db_id'), schema=kwargs.get('schema')))
+ def get_view_names(cls, inspector, db_id, schema,
+ enable_cache, cache_timeout, force=False):
+ return sorted(inspector.get_view_names(schema))
+
+ @classmethod
def where_latest_partition(
cls, table_name, schema, database, qry, columns=None):
return False
@@ -438,7 +447,11 @@ class PostgresEngineSpec(PostgresBaseEngineSpec):
engine = 'postgresql'
@classmethod
- def get_table_names(cls, schema, inspector):
+ @cache_util.memoized_func(
+ key=lambda *args, **kwargs:
'db:{db_id}:schema:{schema}:table_list'.format(
+ db_id=kwargs.get('db_id'), schema=kwargs.get('schema')))
+ def get_table_names(cls, inspector, db_id, schema,
+ enable_cache, cache_timeout, force=False):
"""Need to consider foreign tables for PostgreSQL"""
tables = inspector.get_table_names(schema)
tables.extend(inspector.get_foreign_table_names(schema))
@@ -570,7 +583,6 @@ class SqliteEngineSpec(BaseEngineSpec):
@classmethod
@cache_util.memoized_func(
- timeout=600,
key=lambda *args, **kwargs: 'db:{}:{}'.format(args[0].id, args[1]),
use_tables_cache=True)
def fetch_result_sets(cls, db, datasource_type, force=False):
@@ -596,7 +608,11 @@ class SqliteEngineSpec(BaseEngineSpec):
return "'{}'".format(iso)
@classmethod
- def get_table_names(cls, schema, inspector):
+ @cache_util.memoized_func(
+ key=lambda *args, **kwargs:
'db:{db_id}:schema:{schema}:table_list'.format(
+ db_id=kwargs.get('db_id'), schema=kwargs.get('schema')))
+ def get_table_names(cls, inspector, db_id, schema,
+ enable_cache, cache_timeout, force=False):
"""Need to disregard the schema for Sqlite"""
return sorted(inspector.get_table_names())
@@ -721,7 +737,6 @@ class PrestoEngineSpec(BaseEngineSpec):
@classmethod
@cache_util.memoized_func(
- timeout=600,
key=lambda *args, **kwargs: 'db:{}:{}'.format(args[0].id, args[1]),
use_tables_cache=True)
def fetch_result_sets(cls, db, datasource_type, force=False):
@@ -1003,7 +1018,6 @@ class HiveEngineSpec(PrestoEngineSpec):
@classmethod
@cache_util.memoized_func(
- timeout=600,
key=lambda *args, **kwargs: 'db:{}:{}'.format(args[0].id, args[1]),
use_tables_cache=True)
def fetch_result_sets(cls, db, datasource_type, force=False):
@@ -1461,8 +1475,6 @@ class ImpalaEngineSpec(BaseEngineSpec):
@classmethod
@cache_util.memoized_func(
- enable_cache=lambda *args, **kwargs: kwargs.get('enable_cache', False),
- timeout=lambda *args, **kwargs: kwargs.get('cache_timeout'),
key=lambda *args, **kwargs:
'db:{}:schema_list'.format(kwargs.get('db_id')))
def get_schema_names(cls, inspector, db_id,
enable_cache, cache_timeout, force=False):
diff --git a/superset/models/core.py b/superset/models/core.py
index 7e1aaf4..2bc0206 100644
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -847,8 +847,18 @@ class Database(Model, AuditMixinNullable, ImportMixin):
tables_dict = self.db_engine_spec.fetch_result_sets(
self, 'table', force=force)
return tables_dict.get('', [])
- return sorted(
- self.db_engine_spec.get_table_names(schema, self.inspector))
+
+ extra = self.get_extra()
+ medatada_cache_timeout = extra.get('metadata_cache_timeout', {})
+ table_cache_timeout = medatada_cache_timeout.get('table_cache_timeout')
+ enable_cache = 'table_cache_timeout' in medatada_cache_timeout
+ return sorted(self.db_engine_spec.get_table_names(
+ inspector=self.inspector,
+ db_id=self.id,
+ schema=schema,
+ enable_cache=enable_cache,
+ cache_timeout=table_cache_timeout,
+ force=force))
def all_view_names(self, schema=None, force=False):
if not schema:
@@ -859,7 +869,17 @@ class Database(Model, AuditMixinNullable, ImportMixin):
return views_dict.get('', [])
views = []
try:
- views = self.inspector.get_view_names(schema)
+ extra = self.get_extra()
+ medatada_cache_timeout = extra.get('metadata_cache_timeout', {})
+ table_cache_timeout =
medatada_cache_timeout.get('table_cache_timeout')
+ enable_cache = 'table_cache_timeout' in medatada_cache_timeout
+ views = self.db_engine_spec.get_view_names(
+ inspector=self.inspector,
+ db_id=self.id,
+ schema=schema,
+ enable_cache=enable_cache,
+ cache_timeout=table_cache_timeout,
+ force=force)
except Exception:
pass
return views
diff --git a/superset/views/core.py b/superset/views/core.py
index 43f581e..f7806e8 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -206,7 +206,8 @@ class DatabaseView(SupersetModelView, DeleteMixin,
YamlExportMixin): # noqa
'#sqlalchemy.schema.MetaData) call.<br/>'
'2. The ``metadata_cache_timeout`` is a cache timeout setting '
'in seconds for metadata fetch of this database. Specify it as '
- '**"metadata_cache_timeout": {"schema_cache_timeout": 600}**. '
+ '**"metadata_cache_timeout": {"schema_cache_timeout": 600, '
+ '"table_cache_timeout": 600}**. '
'If unset, cache will not be enabled for the functionality. '
'A timeout of 0 indicates that the cache never expires.<br/>'
'3. The ``schemas_allowed_for_csv_upload`` is a comma separated
list '
@@ -1538,7 +1539,7 @@ class Superset(BaseSupersetView):
@has_access_api
@expose('/schemas/<db_id>/')
@expose('/schemas/<db_id>/<force_refresh>/')
- def schemas(self, db_id, force_refresh='true'):
+ def schemas(self, db_id, force_refresh='false'):
db_id = int(db_id)
force_refresh = force_refresh.lower() == 'true'
database = (
@@ -1556,16 +1557,18 @@ class Superset(BaseSupersetView):
@api
@has_access_api
@expose('/tables/<db_id>/<schema>/<substr>/')
- def tables(self, db_id, schema, substr):
+ @expose('/tables/<db_id>/<schema>/<substr>/<force_refresh>/')
+ def tables(self, db_id, schema, substr, force_refresh='false'):
"""Endpoint to fetch the list of tables for given database"""
db_id = int(db_id)
+ force_refresh = force_refresh.lower() == 'true'
schema = utils.js_string_to_python(schema)
substr = utils.js_string_to_python(substr)
database = db.session.query(models.Database).filter_by(id=db_id).one()
table_names = security_manager.accessible_by_user(
- database, database.all_table_names(schema), schema)
+ database, database.all_table_names(schema, force_refresh), schema)
view_names = security_manager.accessible_by_user(
- database, database.all_view_names(schema), schema)
+ database, database.all_view_names(schema, force_refresh), schema)
if substr:
table_names = [tn for tn in table_names if substr in tn]