Saturday, February 28, 2009

Automatic JavaScript Documenting

The main idea is to help in creating documents like this:
It is possible to automatically document JavaScript by several ways:
  • by JavaScript itself
  • by external JavaScript parser
To automatically document JavaScript by JavaScript itself next approach could be used:
<head>
<script type="text/javascript" src="js/prototype-1.6.0.3.js"></script>

<script type="text/javascript">
// document2str
// document object to string
// params:
// name - object name
// returns
// object documentation as string
function document2str(name) {
var inner = function(obj, tab, res){
for(var i in obj) {
if (typeof(obj[i])=='function') {
var s = obj[i].toString();
var l = s.indexOf('(');
res += tab+i + ' ' + 'function' + s.substr(l, s.indexOf(')')-l+1) + "\n";
} else {
res += tab+i + ' ' + typeof(obj[i]) + "\n";
if (typeof(obj[i])=='object')
res = inner(obj[i], tab+' ', res);
}
}
return res;
}
return inner(eval(name), '', name+"\n");
}

// document2obj
// document object as object
// params:
// name - object name
// returns
// object documentation as object
function document2obj(str) {
var inner = function(obj, res){
for(var i in obj) {
if (typeof(obj[i])=='function') {
var s = obj[i].toString();
var l = s.indexOf('(');
res[i] = 'function' + s.substr(l, s.indexOf(')')-l+1);
} else {
res[i] = typeof(obj[i]);
if (typeof(obj[i])=='object')
res[i] = inner(obj[i], {});
}
}
return res;
}
return inner(eval(str), {});
}
</script>
</head>

<body>
<pre id="pre1">
</pre>

<pre id="pre2">
</pre>

<script type="text/javascript">
$('pre1').innerHTML = document2str('Form');

var obj = document2obj('Form');
$('pre2').innerHTML = Object.toJSON(obj);
</script>
</body>

document2str output:
Form
reset function(form)
serializeElements function(elements, options)
Methods object
serialize function(form, options)
getElements function(form)
getInputs function(form, typeName, name)
disable function(form)
enable function(form)
findFirstElement function(form)
focusFirstElement function(form)
request function(form, options)
Element object
focus function(element)
select function(element)
Methods object
serialize function(element)
getValue function(element)
setValue function(element, value)
clear function(element)
present function(element)
activate function(element)
disable function(element)
enable function(element)
Serializers object
input function(element, value)
inputSelector function(element, value)
textarea function(element, value)
select function(element, value)
selectOne function(element)
selectMany function(element)
optionValue function(opt)
Observer function()
EventObserver function()
serialize function(element)
getValue function(element)
setValue function(element, value)
clear function(element)
present function(element)
activate function(element)
disable function(element)
enable function(element)
Observer function()
EventObserver function()
serialize function(form, options)
getElements function(form)
getInputs function(form, typeName, name)
disable function(form)
enable function(form)
findFirstElement function(form)
focusFirstElement function(form)
request function(form, options)

document2obj output:
{"reset": "function(form)", "serializeElements": "function(elements, options)", "Methods": {"serialize": "function(form, options)", "getElements": "function(form)", "getInputs": "function(form, typeName, name)", "disable": "function(form)", "enable": "function(form)", "findFirstElement": "function(form)", "focusFirstElement": "function(form)", "request": "function(form, options)"}, "Element": {"focus": "function(element)", "select": "function(element)", "Methods": {"serialize": "function(element)", "getValue": "function(element)", "setValue": "function(element, value)", "clear": "function(element)", "present": "function(element)", "activate": "function(element)", "disable": "function(element)", "enable": "function(element)"}, "Serializers": {"input": "function(element, value)", "inputSelector": "function(element, value)", "textarea": "function(element, value)", "select": "function(element, value)", "selectOne": "function(element)", "selectMany": "function(element)", "optionValue": "function(opt)"}, "Observer": "function()", "EventObserver": "function()", "serialize": "function(element)", "getValue": "function(element)", "setValue": "function(element, value)", "clear": "function(element)", "present": "function(element)", "activate": "function(element)", "disable": "function(element)", "enable": "function(element)"}, "Observer": "function()", "EventObserver": "function()", "serialize": "function(form, options)", "getElements": "function(form)", "getInputs": "function(form, typeName, name)", "disable": "function(form)", "enable": "function(form)", "findFirstElement": "function(form)", "focusFirstElement": "function(form)", "request": "function(form, options)"}

Using of JavaScript itself has some limitations.
It hides object creation details, like initialize in Prototype.

To automatically document JavaScript by external JavaScript parser Rhino project could be used.

function compileImpl of a class org.mozilla.javascript.Context contains call of JavaScript parser:
ScriptOrFnNode tree;
if (sourceString != null) {
tree = p.parse(sourceString, sourceName, lineno);
} else {
tree = p.parse(sourceReader, sourceName, lineno);
}

After parsing tree contains list of symbols and functions that are available through:
public final FunctionNode getFunctionNode(int i)
public Map<String,Symbol> getSymbolTable()

Also parsed tree of tokens could be traversed by using:
public boolean hasChildren() {
return first != null;
}

public Node getFirstChild() {
return first;
}

public Node getLastChild() {
return last;
}

public Node getNext() {
return next;
}

Friday, February 27, 2009

BASH functions with multiple results

BASH functions are limited...
Check Bash-Prog-Intro-HOWTO

... but sometimes it is required to have BASH functions that returns multiple results.
In case of strings or numbers next idea could be applied:
#!/bin/bash

# get_rtrn
# get return result part
# params:
# $1 - return result string
# $2 - number of part to be returned
# returns:
# return result part
function get_rtrn(){
echo `echo $1|cut --delimiter=, -f $2`
}

function some_func(){
# calculate result 1
# ...
RES1="string1"

# calculate result 2
# ...
RES2="13"

# calculate result 3
# ...
RES3="value3"

# return results
echo "$RES1,$RES2,$RES3"
}

# function call
RESULT=`some_func`
echo "RESULT = $RESULT"

# get parts of result
RES1=`get_rtrn $RESULT 1`
RES2=`get_rtrn $RESULT 2`
RES3=`get_rtrn $RESULT 3`

echo "RES1 = $RES1"
echo "RES2 = $RES2"
echo "RES3 = $RES3"

Script output:
bash test.sh
RESULT = string1,13,value3
RES1 = string1
RES2 = 13
RES3 = value3

Thursday, February 26, 2009

Migrating to MySQL

If you plan to migrate to MySQL then next advices could be helpful for you:
  1. Be famous how AUTO_INCREMENT works in MySQL
    MySQL 5.0 Reference Manual :: 3 Tutorial :: 3.6 Examples of Common Queries :: 3.6.9 Using AUTO_INCREMENT

    Check how SQL Mode NO_AUTO_VALUE_ON_ZERO affects AUTO_INCREMENT
    MySQL 5.0 Reference Manual :: 5 MySQL Server Administration :: 5.1 The MySQL Server :: 5.1.7 Server SQL Modes

    Inserting 0 to auto-numbered collumn gives next result, i.e. 0 will be replaced by next autonumbered value.


  2. JPA Generator

    MySQL doesn't support:
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "xxx")

    It will throw exception:
    Caused by: org.hibernate.MappingException: Dialect does not support sequences

    It should be changed to
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "xxx")

  3. The default transaction isolation level of InnoDB is REPEATABLE READ
    MySQL 5.0 Reference Manual :: 13 Storage Engines :: 13.2 The InnoDB Storage Engine :: 13.2.8 The InnoDB Transaction Model and Locking

  4. MySQL date/time types have bad precision
    MySQL 5.0 Reference Manual :: 10 Data Types :: 10.5 Data Type Storage Requirements

    So think about changing data type to Long

  5. Be famous with Restrictions on Subqueries
    MySQL 5.0 Reference Manual :: F Restrictions and Limits :: F.3 Restrictions on Subqueries
    In general, you cannot modify a table and select from the same table in a subquery. For example, this limitation applies to statements of the following forms:

    DELETE FROM t WHERE ... (SELECT ... FROM t ...);
    UPDATE t ... WHERE col = (SELECT ... FROM t ...);
    {INSERT|REPLACE} INTO t (SELECT ... FROM t ...);

    Exception: The preceding prohibition does not apply if you are using a subquery for the modified table in the FROM clause. Example:

    UPDATE t ... WHERE col = (SELECT (SELECT ... FROM t...) AS _t ...);

    Here the prohibition does not apply because the result from a subquery in the FROM clause is stored as a temporary table, so the relevant rows in t have already been selected by the time the update to t takes place.

    Some useful functions for JPA/Hibernate that supports this wrapping are listed below:
    private static final Pattern SUBQUERY_ON_MUTATING_TABLE_PATTERN = Pattern.compile("^(.*?)(\\(\\s*SELECT\\s+)(.*?)$", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    public static int wrappedExecuteUpdate(EntityManager entityManager, Query query, Map<String, Object> parameters) {
    SessionFactoryImplementor sessionFactory = getHibernateSessionFactoryImplementor(entityManager);

    // if (!sessionFactory.getDialect().supportsSubqueryOnMutatingTable()) {
    if (sessionFactory.getDialect() instanceof MySQLDialect) {
    return doesntSupportSubqueryOnMutatingTable(sessionFactory, entityManager, query, parameters);
    } else {
    return supportsSubqueryOnMutatingTable(entityManager, query, parameters);
    }
    }

    private static int supportsSubqueryOnMutatingTable(EntityManager entityManager, Query query, Map<String, Object> parameters) {
    for (String p : parameters.keySet()) {
    query.setParameter(p, parameters.get(p));
    }
    return query.executeUpdate();
    }

    private static int doesntSupportSubqueryOnMutatingTable(SessionFactoryImplementor sessionFactory, EntityManager entityManager, Query query, Map<String, Object> parameters) {
    org.hibernate.impl.QueryImpl queryImpl = getHibernateQueryImpl(query);

    QueryTranslatorImpl qt = (QueryTranslatorImpl) new ASTQueryTranslatorFactory().createQueryTranslator(
    "tempTableWrapping", queryImpl.getQueryString(), null, sessionFactory);

    qt.compile(null, false);
    String sql;
    if (qt.getSqlAST().needsExecutor()) {
    sql = qt.collectSqlStrings().get(0).toString();
    } else {
    sql = qt.getSQLString();
    }

    // org.hibernate.ejb.QueryImpl could handle Collection parameters right ONLY for named parameters
    // (setParameterList supported only for named parameters)
    // so we need to switch from positional parameters to named parameters
    // (we have positional parameters after QueryTranslator.compile)
    ParameterTranslations pt = qt.getParameterTranslations();
    sql = switchToNamedParameters(sql, pt);

    Query newQuery = entityManager.createNativeQuery(getTempTableWrapping(sql, "_t"));

    for (String p : parameters.keySet()) {
    newQuery.setParameter(p, parameters.get(p));
    }
    return newQuery.executeUpdate();
    }

    @SuppressWarnings("unchecked")
    private static String switchToNamedParameters(String sql, ParameterTranslations pt) {
    String[] parts = sql.split("\\?");
    for (String p : (Set<String>) pt.getNamedParameterNames()) {
    int locations[] = pt.getNamedParameterSqlLocations(p);
    for (int l : locations) {
    parts[l] = parts[l] + ":" + p;
    }
    }
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < parts.length; i++) {
    sb.append(parts[i]);
    }
    return sb.toString();
    }

    Also is not supported:

    WHERE (X, Y) IN (SELECT X, Y FROM ...)

Tuesday, February 24, 2009

Stored Procedures in MySQL 5.x

As you know MySQL 5.0 introduced Stored Procedures which allow us to automate or program our way out of many tasks on directly on the server rather than having to write external scripts to do complex manipulation of data.
You can find some useful procedures and functions below.
The most important are:
  • execute dynamic SQL statement
  • raise error/exception
DELIMITER $$


-- PROCEDURE pExecuteImmediate
-- executes dynamic SQL statement
-- Params:
-- tSQLStmt - SQL statement to be executed
DROP PROCEDURE IF EXISTS `pExecuteImmediate` $$
CREATE PROCEDURE `pExecuteImmediate`(IN tSQLStmt TEXT)
BEGIN
SET @executeImmediateSQL = tSQLStmt;
PREPARE executeImmediateSTML FROM @executeImmediateSQL;
EXECUTE executeImmediateSTML;
DEALLOCATE PREPARE executeImmediateSTML;
END $$


-- FUNCTION fFormat
-- replaces '%s' in a string with some value
-- Params:
-- sFormat - string to be formatted
-- sPar1 - value used in replacement
-- Returns:
-- formatted string
DROP FUNCTION IF EXISTS `fFormat` $$
CREATE FUNCTION fFormat(sFormat TEXT, sPar1 TEXT)
RETURNS TEXT
BEGIN
RETURN REPLACE(sFormat, '%s', sPar1);
END $$


-- PROCEDURE pRaiseError
-- raises error
-- Params:
-- sError - error message
DROP PROCEDURE IF EXISTS `pRaiseError` $$
CREATE PROCEDURE `pRaiseError`(sError VARCHAR(255))
BEGIN
-- trick
-- calling of not existing procedure with name that equals error message
-- will force MySQL exception that looks like error message
CALL pExecuteImmediate(fFormat('CALL `error: %s, solution`', sError));
END $$


-- FUNCTION fIsCurrentUserRoot
-- checks if we are connected as root user
-- Returns:
-- 0 - table doesn't exist
-- 1 - table exists
DROP FUNCTION IF EXISTS `fIsCurrentUserRoot` $$
CREATE FUNCTION `fIsCurrentUserRoot`()
RETURNS INT
BEGIN
DECLARE strUser VARCHAR(50);
DECLARE iRes INT;
SELECT USER() INTO strUser;
IF (strUser LIKE 'root%') THEN
SET iRes = 1;
ELSE
SET iRes = 0;
END IF;
RETURN iRes;
END $$


-- FUNCTION fIsTableExists
-- checks if table exists
-- Params:
-- vcTableName - table name
-- Returns:
-- 0 - table doesn't exist
-- 1 - table exists
DROP FUNCTION IF EXISTS `fIsTableExists` $$
CREATE FUNCTION `fIsTableExists`(vcTableName VARCHAR(50))
RETURNS INT
BEGIN
DECLARE iCount INT;
SELECT count(*) INTO iCount FROM INFORMATION_SCHEMA.TABLES WHERE table_name LIKE vcTableName AND table_type = 'BASE TABLE';
RETURN iCount;
END $$


-- FUNCTION fIsViewExists
-- checks if view exists
-- Params:
-- vcViewName - view name
-- Returns:
-- 0 - view doesn't exist
-- 1 - view exists
DROP FUNCTION IF EXISTS `fIsViewExists` $$
CREATE FUNCTION `fIsViewExists`(vcViewName VARCHAR(50))
RETURNS INT
BEGIN
DECLARE iCount INT;
SELECT count(*) INTO iCount FROM INFORMATION_SCHEMA.VIEWS WHERE table_name LIKE vcViewName;
RETURN iCount;
END $$


-- FUNCTION fIsIndexExists
-- checks if index exists
-- Params:
-- vcTableName - table name
-- vcIndexName - index name
-- Returns:
-- 0 - index doesn't exist
-- 1 - index exists
DROP FUNCTION IF EXISTS `fIsIndexExists` $$
CREATE FUNCTION `fIsIndexExists`(vcTableName VARCHAR(50), vcIndexName VARCHAR(50))
RETURNS INT
BEGIN
DECLARE iCount INT;
SELECT count(*) INTO iCount FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_name LIKE vcTableName AND index_name LIKE vcIndexName;
RETURN iCount;
END $$


-- FUNCTION fIsColumnExists
-- checks if column exists
-- Params:
-- vcTableName - table name
-- vcColumnName - column name
-- Returns:
-- 0 - column doesn't exist
-- 1 - column exists
DROP FUNCTION IF EXISTS `fIsColumnExists` $$
CREATE FUNCTION `fIsColumnExists`(vcTableName VARCHAR(50), vcColumnName VARCHAR(50))
RETURNS INT
BEGIN
DECLARE iCount INT;
SELECT count(*) INTO iCount FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name LIKE vcTableName AND column_name LIKE vcColumnName;
RETURN iCount;
END $$


DELIMITER ;

Some examples how these procedures and functions can be used...

..from other procedures/functions:
DELIMITER $$


-- PROCEDURE pDropTable
-- drops table only if it exists
-- Params:
-- vcTableName - table name
DROP PROCEDURE IF EXISTS `pDropTable` $$
CREATE PROCEDURE `pDropTable`(IN vcTableName VARCHAR(50))
BEGIN
IF (fIsTableExists(vcTableName)=1) THEN
CALL pExecuteImmediate(CONCAT('DROP TABLE ', vcTableName));
END IF;
END $$


-- PROCEDURE pRenameTable
-- renames table only if it exists
-- Params:
-- vcTableName - old table name
-- vcNewTableName - new table name
DROP PROCEDURE IF EXISTS `pRenameTable` $$
CREATE PROCEDURE `pRenameTable`(IN vcTableName VARCHAR(50), IN vcNewTableName VARCHAR(50))
BEGIN
IF (fIsTableExists(vcTableName)=1) THEN
CALL pExecuteImmediate(CONCAT('ALTER TABLE ', vcTableName, ' RENAME TO ', vcNewTableName));
END IF;
END $$


-- PROCEDURE pCheckCurrentUserRoot
-- raise exception if current user is root
DROP PROCEDURE IF EXISTS `pCheckCurrentUserRoot` $$
CREATE PROCEDURE `pCheckCurrentUserRoot`()
BEGIN
IF (fIsCurrentUserRoot()=1) THEN
CALL pRaiseError('Root user is not allowed to run this script');
END IF;
END $$


DELIMITER ;

...or from SQL:

SELECT fIsTableExists('test');


SELECT fIsCurrentUserRoot();


CALL pExecuteImmediate('SELECT 1');


CALL pCheckCurrentUserRoot();

Sunday, February 15, 2009

Tag Cloud

If you like Tag Cloud more than standard Labels widget check out:
I've applied the second one for my blog.

Formatting Source Code For Blog Posts

Sometimes it is required to format your source code for blog posting.
Check links below:
I've selected for myself CodeHTMLer with next options:
  • Type of Style: Style Classes
  • Whitespace Options:
Then I added styles to my blog template. See results below:

Java class Node
package source; 

import java.util.LinkedList;
import java.util.List;

public class Node {
public List<Node> nodes = new LinkedList<Node>();
public int data;
public boolean isMarked;

public Node(int data) {
this.data = data;
isMarked = false;
}

public void add(Node node) {
nodes.add(node);
}
}

Java class Graph
package source; 

import java.util.LinkedList;
import java.util.Queue;

public class Graph {
public Node root;

public Graph(Node root) {
this.root = root;
}

public void dfs() {
dfs(root);
System.out.println();
}

private void dfs(Node node) {
if (node == null)
return;

System.out.print(node.data+" ");

for(Node n: node.nodes) {
dfs(n);
}
}

public void bfs() {
Queue<Node> queue = new LinkedList<Node>();

queue.add(root);

bfs(queue);
System.out.println();
}

private void bfs(Queue<Node> queue) {
while (!queue.isEmpty()) {
Node node = queue.remove();
System.out.print(node.data+" ");

for(Node n: node.nodes) {
queue.add(n);
}
}
}
}

Other useful formatter is c# code format