From 35b376aad888579867105a7a0c69ed3403b6196d Mon Sep 17 00:00:00 2001 From: Vlad Ilyushchenko Date: Tue, 17 May 2016 18:01:23 +0100 Subject: [PATCH] initial cut of query export --- .../main/java/com/questdb/BootstrapMain.java | 42 +- .../net/http/handlers/ExportHandler.java | 394 ++++++++++++++++++ .../net/http/handlers/QueryHandler.java | 4 +- .../src/main/resources/site/public/index.html | 2 +- .../main/resources/site/public/scripts/qdb.js | 16 +- ui/app/index.html | 3 +- ui/app/scripts/grid.js | 6 + 7 files changed, 440 insertions(+), 27 deletions(-) create mode 100644 core/src/main/java/com/questdb/net/http/handlers/ExportHandler.java diff --git a/core/src/main/java/com/questdb/BootstrapMain.java b/core/src/main/java/com/questdb/BootstrapMain.java index c0a9bd197..935a04a25 100644 --- a/core/src/main/java/com/questdb/BootstrapMain.java +++ b/core/src/main/java/com/questdb/BootstrapMain.java @@ -5,19 +5,32 @@ * | |_| | |_| | __/\__ \ |_| |_| | |_) | * \__\_\\__,_|\___||___/\__|____/|____/ * - * Copyright (c) 2014-2016 Appsicle + * Copyright (C) 2014-2016 Appsicle * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. ******************************************************************************/ package com.questdb; @@ -32,10 +45,7 @@ import com.questdb.net.http.HttpServer; import com.questdb.net.http.HttpServerConfiguration; import com.questdb.net.http.MimeTypes; import com.questdb.net.http.SimpleUrlMatcher; -import com.questdb.net.http.handlers.ExistenceCheckHandler; -import com.questdb.net.http.handlers.ImportHandler; -import com.questdb.net.http.handlers.QueryHandler; -import com.questdb.net.http.handlers.StaticContentHandler; +import com.questdb.net.http.handlers.*; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; @@ -80,8 +90,10 @@ class BootstrapMain { final SimpleUrlMatcher matcher = new SimpleUrlMatcher(); JournalFactory factory = new JournalFactory(configuration.getDbPath().getAbsolutePath()); + JournalFactoryPool pool = new JournalFactoryPool(factory.getConfiguration(), configuration.getJournalPoolSize()); matcher.put("/imp", new ImportHandler(factory)); - matcher.put("/js", new QueryHandler(new JournalFactoryPool(factory.getConfiguration(), configuration.getJournalPoolSize()))); + matcher.put("/js", new QueryHandler(pool)); + matcher.put("/csv", new ExportHandler(pool)); matcher.put("/chk", new ExistenceCheckHandler(factory)); matcher.setDefaultHandler(new StaticContentHandler(configuration.getHttpPublic(), new MimeTypes(configuration.getMimeTypes()))); diff --git a/core/src/main/java/com/questdb/net/http/handlers/ExportHandler.java b/core/src/main/java/com/questdb/net/http/handlers/ExportHandler.java new file mode 100644 index 000000000..3d15f9daa --- /dev/null +++ b/core/src/main/java/com/questdb/net/http/handlers/ExportHandler.java @@ -0,0 +1,394 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2016 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + * + ******************************************************************************/ + +package com.questdb.net.http.handlers; + +import com.questdb.ex.*; +import com.questdb.factory.JournalCachingFactory; +import com.questdb.factory.JournalFactoryPool; +import com.questdb.factory.configuration.RecordColumnMetadata; +import com.questdb.factory.configuration.RecordMetadata; +import com.questdb.log.Log; +import com.questdb.log.LogFactory; +import com.questdb.log.LogRecord; +import com.questdb.misc.Chars; +import com.questdb.misc.Misc; +import com.questdb.misc.Numbers; +import com.questdb.net.http.ChunkedResponse; +import com.questdb.net.http.ContextHandler; +import com.questdb.net.http.IOContext; +import com.questdb.ql.Record; +import com.questdb.ql.RecordCursor; +import com.questdb.ql.RecordSource; +import com.questdb.ql.parser.QueryError; +import com.questdb.std.CharSink; +import com.questdb.std.LocalValue; +import com.questdb.std.Mutable; +import com.questdb.store.ColumnType; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +import static com.questdb.net.http.handlers.QueryHandler.CACHE; +import static com.questdb.net.http.handlers.QueryHandler.COMPILER; + +public class ExportHandler implements ContextHandler { + private final JournalFactoryPool factoryPool; + private final LocalValue localContext = new LocalValue<>(); + private final AtomicLong cacheHits = new AtomicLong(); + private final AtomicLong cacheMisses = new AtomicLong(); + + public ExportHandler(JournalFactoryPool factoryPool) { + this.factoryPool = factoryPool; + } + + @Override + public void handle(IOContext context) throws IOException { + ExportHandlerContext ctx = localContext.get(context); + if (ctx == null) { + localContext.set(context, ctx = new ExportHandlerContext()); + } + ctx.fd = context.channel.getFd(); + + // Query text. + ChunkedResponse r = context.chunkedResponse(); + CharSequence query = context.request.getUrlParam("query"); + if (query == null || query.length() == 0) { + ctx.info().$("Empty query request received. Sending empty reply.").$(); + header(r, 200); + r.done(); + return; + } + + // Url Params. + long skip = 0; + long stop = Long.MAX_VALUE; + + CharSequence limit = context.request.getUrlParam("limit"); + if (limit != null) { + int sepPos = Chars.indexOf(limit, ','); + try { + if (sepPos > 0) { + skip = Numbers.parseLong(limit, 0, sepPos); + if (sepPos + 1 < limit.length()) { + stop = Numbers.parseLong(limit, sepPos + 1, limit.length()); + } + } else { + stop = Numbers.parseLong(limit); + } + } catch (NumericException ex) { + // Skip or stop will have default value. + } + } + if (stop < 0) { + stop = 0; + } + + if (skip < 0) { + skip = 0; + } + + ctx.query = query; + ctx.skip = skip; + ctx.count = 0L; + ctx.stop = stop; + + ctx.info().$("Query: ").$(query). + $(", skip: ").$(skip). + $(", stop: ").$(stop).$(); + + executeQuery(r, ctx); + resume(context); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void resume(IOContext context) throws IOException { + ExportHandlerContext ctx = localContext.get(context); + if (ctx == null || ctx.cursor == null) { + return; + } + + final ChunkedResponse r = context.chunkedResponse(); + final int columnCount = ctx.metadata.getColumnCount(); + + OUT: + while (true) { + try { + SWITCH: + switch (ctx.state) { + case METADATA: + for (; ctx.columnIndex < columnCount; ctx.columnIndex++) { + RecordColumnMetadata column = ctx.metadata.getColumnQuick(ctx.columnIndex); + + r.bookmark(); + if (ctx.columnIndex > 0) { + r.put(','); + } + r.putQuoted(column.getName()); + } + r.put(Misc.EOL); + ctx.state = QueryState.RECORD_START; + // fall through + case RECORD_START: + if (ctx.record == null) { + // check if cursor has any records + while (true) { + if (ctx.cursor.hasNext()) { + ctx.record = ctx.cursor.next(); + ctx.count++; + + if (ctx.count > ctx.skip) { + break; + } + } else { + ctx.state = QueryState.DATA_SUFFIX; + break SWITCH; + } + } + } + + if (ctx.count > ctx.stop) { + ctx.state = QueryState.DATA_SUFFIX; + break; + } + + ctx.state = QueryState.RECORD_COLUMNS; + ctx.columnIndex = 0; + // fall through + case RECORD_COLUMNS: + + for (; ctx.columnIndex < columnCount; ctx.columnIndex++) { + RecordColumnMetadata m = ctx.metadata.getColumnQuick(ctx.columnIndex); + r.bookmark(); + if (ctx.columnIndex > 0) { + r.put(','); + } + putValue(r, m.getType(), ctx.record, ctx.columnIndex); + } + + r.put(Misc.EOL); + ctx.record = null; + ctx.state = QueryState.RECORD_START; + break; + case DATA_SUFFIX: + sendDone(r, ctx); + break OUT; + default: + break OUT; + } + } catch (ResponseContentBufferTooSmallException ignored) { + if (r.resetToBookmark()) { + r.sendChunk(); + } else { + // what we have here is out unit of data, column value or query + // is larger that response content buffer + // all we can do in this scenario is to log appropriately + // and disconnect socket + ctx.info().$("Response buffer is too small, state=").$(ctx.state).$(); + throw DisconnectedChannelException.INSTANCE; + } + } + } + } + + private static void sendException(ChunkedResponse r, int position, CharSequence message, int status) throws DisconnectedChannelException, SlowWritableChannelException { + header(r, status); + r.put("Error at(").put(position).put("): ").put(message).put(Misc.EOL); + r.sendChunk(); + r.done(); + } + + private static void putValue(CharSink sink, ColumnType type, Record rec, int col) { + switch (type) { + case BOOLEAN: + sink.put(rec.getBool(col)); + break; + case BYTE: + sink.put(rec.get(col)); + break; + case DOUBLE: + double d = rec.getDouble(col); + if (d == d) { + sink.put(d, 10); + } + break; + case FLOAT: + float f = rec.getFloat(col); + if (f == f) { + sink.put(f, 10); + } + break; + case INT: + final int i = rec.getInt(col); + if (i > Integer.MIN_VALUE) { + Numbers.append(sink, i); + } + break; + case LONG: + final long l = rec.getLong(col); + if (l > Long.MIN_VALUE) { + sink.put(l); + } + break; + case DATE: + final long dt = rec.getDate(col); + if (dt > Long.MIN_VALUE) { + sink.put('"').putISODate(dt).put('"'); + } + break; + case SHORT: + sink.put(rec.getShort(col)); + break; + case STRING: + CharSequence cs; + cs = rec.getFlyweightStr(col); + if (cs != null) { + sink.put(cs); + } + break; + case SYMBOL: + cs = rec.getSym(col); + if (cs != null) { + sink.put(cs); + } + break; + case BINARY: + break; + default: + break; + } + } + + private static void header(ChunkedResponse r, int code) throws DisconnectedChannelException, SlowWritableChannelException { + r.status(code, "text/csv; charset=utf-8"); + r.headers().put("Content-Disposition: attachment; filename=\"questdb-query-").put(System.currentTimeMillis()).put(".csv\"").put(Misc.EOL); + r.sendHeader(); + } + + private void executeQuery(ChunkedResponse r, ExportHandlerContext ctx) throws IOException { + try { + // Prepare Context. + JournalCachingFactory factory = factoryPool.get(); + ctx.factory = factory; + ctx.recordSource = CACHE.get().poll(ctx.query); + if (ctx.recordSource == null) { + ctx.recordSource = COMPILER.get().compileSource(factory, ctx.query); + cacheMisses.incrementAndGet(); + } else { + ctx.recordSource.reset(); + cacheHits.incrementAndGet(); + } + ctx.cursor = ctx.recordSource.prepareCursor(factory); + ctx.metadata = ctx.cursor.getMetadata(); + ctx.state = QueryState.METADATA; + ctx.columnIndex = 0; + + header(r, 200); + } catch (ParserException e) { + ctx.info().$("Parser error executing query ").$(ctx.query).$(": at (").$(QueryError.getPosition()).$(") ").$(QueryError.getMessage()).$(); + sendException(r, QueryError.getPosition(), QueryError.getMessage(), 400); + } catch (JournalException e) { + ctx.error().$("Server error executing query ").$(ctx.query).$(e).$(); + sendException(r, 0, e.getMessage(), 500); + } catch (InterruptedException e) { + ctx.error().$("Error executing query. Server is shutting down. Query: ").$(ctx.query).$(e).$(); + sendException(r, 0, "Server is shutting down.", 500); + } + } + + private void sendDone(ChunkedResponse r, ExportHandlerContext ctx) throws DisconnectedChannelException, SlowWritableChannelException { + if (ctx.count > -1) { + ctx.count = -1; + r.sendChunk(); + } + r.done(); + } + + private enum QueryState { + METADATA, RECORD_START, RECORD_COLUMNS, DATA_SUFFIX + } + + private static class ExportHandlerContext implements Mutable, Closeable { + private static final Log LOG = LogFactory.getLog(ExportHandlerContext.class); + private RecordSource recordSource; + private CharSequence query; + private RecordMetadata metadata; + private RecordCursor cursor; + private long count; + private long skip; + private long stop; + private Record record; + private JournalCachingFactory factory; + private long fd; + private QueryState state = QueryState.METADATA; + private int columnIndex; + + @Override + public void clear() { + debug().$("Cleaning context").$(); + metadata = null; + cursor = null; + record = null; + debug().$("Closing journal factory").$(); + factory = Misc.free(factory); + if (recordSource != null) { + CACHE.get().put(query.toString(), recordSource); + recordSource = null; + } + query = null; + state = QueryState.METADATA; + } + + @Override + public void close() throws IOException { + debug().$("Closing context").$(); + clear(); + } + + private LogRecord debug() { + return LOG.debug().$('[').$(fd).$("] "); + } + + private LogRecord error() { + return LOG.error().$('[').$(fd).$("] "); + } + + private LogRecord info() { + return LOG.info().$('[').$(fd).$("] "); + } + } +} diff --git a/core/src/main/java/com/questdb/net/http/handlers/QueryHandler.java b/core/src/main/java/com/questdb/net/http/handlers/QueryHandler.java index 467e43f0b..e574dd74a 100644 --- a/core/src/main/java/com/questdb/net/http/handlers/QueryHandler.java +++ b/core/src/main/java/com/questdb/net/http/handlers/QueryHandler.java @@ -63,13 +63,13 @@ import java.io.IOException; import java.util.concurrent.atomic.AtomicLong; public class QueryHandler implements ContextHandler { - private static final ThreadLocal COMPILER = new ThreadLocal<>(new ObjectFactory() { + public static final ThreadLocal COMPILER = new ThreadLocal<>(new ObjectFactory() { @Override public QueryCompiler newInstance() { return new QueryCompiler(); } }); - private static final ThreadLocal> CACHE = new ThreadLocal<>(new ObjectFactory>() { + public static final ThreadLocal> CACHE = new ThreadLocal<>(new ObjectFactory>() { @Override public AssociativeCache newInstance() { return new AssociativeCache<>(8, 128); diff --git a/core/src/main/resources/site/public/index.html b/core/src/main/resources/site/public/index.html index 1091ffa11..0a30c2d97 100644 --- a/core/src/main/resources/site/public/index.html +++ b/core/src/main/resources/site/public/index.html @@ -29,4 +29,4 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->QuestDB - Console

SQL Console

-

Data import

Drag files here to import

ABCD

 1023003 rows
 15000000 rows
Column name
Type
Errors
Import failed
Server rejected file due to unsupported file format.
\ No newline at end of file + });

SQL Console

-

Data import

Drag files here to import

ABCD

 1023003 rows
 15000000 rows
Column name
Type
Errors
Import failed
Server rejected file due to unsupported file format.
\ No newline at end of file diff --git a/core/src/main/resources/site/public/scripts/qdb.js b/core/src/main/resources/site/public/scripts/qdb.js index 58cf6f0fa..5e9293e33 100644 --- a/core/src/main/resources/site/public/scripts/qdb.js +++ b/core/src/main/resources/site/public/scripts/qdb.js @@ -1,9 +1,9 @@ -function s4(){"use strict";return(65536*(1+Math.random())|0).toString(16).substring(1)}function guid(){"use strict";return s4()+s4()+"-"+s4()+"-"+s4()+"-"+s4()+"-"+s4()+s4()+s4()}function toSize(e){"use strict";return 1024>e?e:1048576>e?Math.round(e/1024)+"KB":1073741824>e?Math.round(e/1024/1024)+"MB":Math.round(e/1024/1024/1024)+"GB"}function nopropagation(e){"use strict";e.stopPropagation(),e.preventDefault&&e.preventDefault()}function localStorageSupport(){"use strict";return"localStorage"in window&&null!==window.localStorage}function fixHeight(){"use strict";var e=$("body > #wrapper").height()-61;$(".sidebard-panel").css("min-height",e+"px");var t=$("nav.navbar-default").height(),n=$("#page-wrapper"),i=n.height();t>i&&n.css("min-height",t+"px"),i>t&&n.css("min-height",$(window).height()+"px"),$("body").hasClass("fixed-nav")&&(t>i?n.css("min-height",t-60+"px"):n.css("min-height",$(window).height()-60+"px"))}if(!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t=!!e&&"length"in e&&e.length,n=re.type(e);return"function"===n||re.isWindow(e)?!1:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function i(e,t,n){if(re.isFunction(t))return re.grep(e,function(e,i){return!!t.call(e,i,e)!==n});if(t.nodeType)return re.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(pe.test(t))return re.filter(t,e,n);t=re.filter(t,e)}return re.grep(e,function(e){return Z.call(t,e)>-1!==n})}function o(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function r(e){var t={};return re.each(e.match(ye)||[],function(e,n){t[n]=!0}),t}function s(){Y.removeEventListener("DOMContentLoaded",s),e.removeEventListener("load",s),re.ready()}function a(){this.expando=re.expando+a.uid++}function l(e,t,n){var i;if(void 0===n&&1===e.nodeType)if(i="data-"+t.replace(De,"-$&").toLowerCase(),n=e.getAttribute(i),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:Se.test(n)?re.parseJSON(n):n}catch(o){}xe.set(e,t,n)}else n=void 0;return n}function c(e,t,n,i){var o,r=1,s=20,a=i?function(){return i.cur()}:function(){return re.css(e,t,"")},l=a(),c=n&&n[3]||(re.cssNumber[t]?"":"px"),h=(re.cssNumber[t]||"px"!==c&&+l)&&ke.exec(re.css(e,t));if(h&&h[3]!==c){c=c||h[3],n=n||[],h=+l||1;do r=r||".5",h/=r,re.style(e,t,h+c);while(r!==(r=a()/l)&&1!==r&&--s)}return n&&(h=+h||+l||0,o=n[1]?h+(n[1]+1)*n[2]:+n[2],i&&(i.unit=c,i.start=h,i.end=o)),o}function h(e,t){var n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&re.nodeName(e,t)?re.merge([e],n):n}function u(e,t){for(var n=0,i=e.length;i>n;n++)$e.set(e[n],"globalEval",!t||$e.get(t[n],"globalEval"))}function d(e,t,n,i,o){for(var r,s,a,l,c,d,f=t.createDocumentFragment(),g=[],p=0,m=e.length;m>p;p++)if(r=e[p],r||0===r)if("object"===re.type(r))re.merge(g,r.nodeType?[r]:r);else if(Ie.test(r)){for(s=s||f.appendChild(t.createElement("div")),a=(_e.exec(r)||["",""])[1].toLowerCase(),l=Oe[a]||Oe._default,s.innerHTML=l[1]+re.htmlPrefilter(r)+l[2],d=l[0];d--;)s=s.lastChild;re.merge(g,s.childNodes),s=f.firstChild,s.textContent=""}else g.push(t.createTextNode(r));for(f.textContent="",p=0;r=g[p++];)if(i&&re.inArray(r,i)>-1)o&&o.push(r);else if(c=re.contains(r.ownerDocument,r),s=h(f.appendChild(r),"script"),c&&u(s),n)for(d=0;r=s[d++];)Me.test(r.type||"")&&n.push(r);return f}function f(){return!0}function g(){return!1}function p(){try{return Y.activeElement}catch(e){}}function m(e,t,n,i,o,r){var s,a;if("object"==typeof t){"string"!=typeof n&&(i=i||n,n=void 0);for(a in t)m(e,a,n,i,t[a],r);return e}if(null==i&&null==o?(o=n,i=n=void 0):null==o&&("string"==typeof n?(o=i,i=void 0):(o=i,i=n,n=void 0)),o===!1)o=g;else if(!o)return e;return 1===r&&(s=o,o=function(e){return re().off(e),s.apply(this,arguments)},o.guid=s.guid||(s.guid=re.guid++)),e.each(function(){re.event.add(this,t,o,i,n)})}function v(e,t){return re.nodeName(e,"table")&&re.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function A(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function C(e){var t=qe.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function w(e,t){var n,i,o,r,s,a,l,c;if(1===t.nodeType){if($e.hasData(e)&&(r=$e.access(e),s=$e.set(t,r),c=r.events)){delete s.handle,s.events={};for(o in c)for(n=0,i=c[o].length;i>n;n++)re.event.add(t,o,c[o][n])}xe.hasData(e)&&(a=xe.access(e),l=re.extend({},a),xe.set(t,l))}}function y(e,t){var n=t.nodeName.toLowerCase();"input"===n&&Re.test(e.type)?t.checked=e.checked:"input"!==n&&"textarea"!==n||(t.defaultValue=e.defaultValue)}function b(e,t,n,i){t=X.apply([],t);var o,r,s,a,l,c,u=0,f=e.length,g=f-1,p=t[0],m=re.isFunction(p);if(m||f>1&&"string"==typeof p&&!ie.checkClone&&je.test(p))return e.each(function(o){var r=e.eq(o);m&&(t[0]=p.call(this,o,r.html())),b(r,t,n,i)});if(f&&(o=d(t,e[0].ownerDocument,!1,e,i),r=o.firstChild,1===o.childNodes.length&&(o=r),r||i)){for(s=re.map(h(o,"script"),A),a=s.length;f>u;u++)l=o,u!==g&&(l=re.clone(l,!0,!0),a&&re.merge(s,h(l,"script"))),n.call(e[u],l,u);if(a)for(c=s[s.length-1].ownerDocument,re.map(s,C),u=0;a>u;u++)l=s[u],Me.test(l.type||"")&&!$e.access(l,"globalEval")&&re.contains(c,l)&&(l.src?re._evalUrl&&re._evalUrl(l.src):re.globalEval(l.textContent.replace(Ue,"")))}return e}function F(e,t,n){for(var i,o=t?re.filter(t,e):e,r=0;null!=(i=o[r]);r++)n||1!==i.nodeType||re.cleanData(h(i)),i.parentNode&&(n&&re.contains(i.ownerDocument,i)&&u(h(i,"script")),i.parentNode.removeChild(i));return e}function E(e,t){var n=re(t.createElement(e)).appendTo(t.body),i=re.css(n[0],"display");return n.detach(),i}function $(e){var t=Y,n=Ke[e];return n||(n=E(e,t),"none"!==n&&n||(Ve=(Ve||re("